This R script is used to validate the points in the ReSurvey database using RS indicators (indices + phenology + canopy height).

Load libraries

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(here)
G3;here() starts at C:/Users/jimenezalfaro/OneDrive - Universidad de Oviedo/IMIB/Analyses/MOTIVATE/MOTIVATE_validation
g
library(gridExtra)
G3;
Adjuntando el paquete: ‘gridExtra’

gG3;The following object is masked from ‘package:dplyr’:

    combine

g
library(readxl)
library(scales)
G3;
Adjuntando el paquete: ‘scales’

gG3;The following object is masked from ‘package:purrr’:

    discard

gG3;The following object is masked from ‘package:readr’:

    col_factor

g
library(sf)
G3;Linking to GEOS 3.13.1, GDAL 3.10.2, PROJ 9.5.1; sf_use_s2() is TRUE
g
library(rnaturalearth)
library(dtplyr)
G3;Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
g
library(lme4)
G3;Cargando paquete requerido: Matrix
gG3;
Adjuntando el paquete: ‘Matrix’

gG3;The following objects are masked from ‘package:tidyr’:

    expand, pack, unpack

g
library(lmerTest)
G3;
Adjuntando el paquete: ‘lmerTest’

gG3;The following object is masked from ‘package:lme4’:

    lmer

gG3;The following object is masked from ‘package:stats’:

    step

g
library(car)
G3;Cargando paquete requerido: carData
gG3;
Adjuntando el paquete: ‘car’

gG3;The following object is masked from ‘package:dplyr’:

    recode

gG3;The following object is masked from ‘package:purrr’:

    some

g
library(ggeffects)
library(party)
G3;Cargando paquete requerido: grid
gG3;Cargando paquete requerido: mvtnorm
gG3;Cargando paquete requerido: modeltools
gG3;Cargando paquete requerido: stats4
gG3;
Adjuntando el paquete: ‘modeltools’

gG3;The following object is masked from ‘package:car’:

    Predict

gG3;The following object is masked from ‘package:lme4’:

    refit

gG3;Cargando paquete requerido: strucchange
gG3;Cargando paquete requerido: zoo
gG3;
Adjuntando el paquete: ‘zoo’

gG3;The following objects are masked from ‘package:base’:

    as.Date, as.Date.numeric

gG3;Cargando paquete requerido: sandwich
gG3;
Adjuntando el paquete: ‘strucchange’

gG3;The following object is masked from ‘package:stringr’:

    boundary

gG3;
Adjuntando el paquete: ‘party’

gG3;The following object is masked from ‘package:dplyr’:

    where

g
library(partykit)
G3;Cargando paquete requerido: libcoin
gG3;
Adjuntando el paquete: ‘partykit’

gG3;The following objects are masked from ‘package:party’:

    cforest, ctree, ctree_control, edge_simple, mob, mob_control,
    node_barplot, node_bivplot, node_boxplot, node_inner, node_surv,
    node_terminal, varimp

g
library(moreparty)
G3;Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
g
library(doParallel)
G3;Cargando paquete requerido: foreach
gG3;
Adjuntando el paquete: ‘foreach’

gG3;The following objects are masked from ‘package:purrr’:

    accumulate, when

gG3;Cargando paquete requerido: iterators
gG3;Cargando paquete requerido: parallel
g
library(strucchange)
library(ggparty)
G3;
Adjuntando el paquete: ‘ggparty’

gG3;The following object is masked from ‘package:ggeffects’:

    get_predictions

g
library(caret)
G3;Cargando paquete requerido: lattice
gG3;
Adjuntando el paquete: ‘caret’

gG3;The following object is masked from ‘package:purrr’:

    lift

g
library(moreparty)
library(randomForest)
G3;randomForest 4.7-1.2
gG3;Type rfNews() to see new features/changes/bug fixes.
gG3;
Adjuntando el paquete: ‘randomForest’

gG3;The following object is masked from ‘package:gridExtra’:

    combine

gG3;The following object is masked from ‘package:dplyr’:

    combine

gG3;The following object is masked from ‘package:ggplot2’:

    margin

g
library(pROC)
G3;Type 'citation("pROC")' for a citation.
gG3;
Adjuntando el paquete: ‘pROC’

gG3;The following objects are masked from ‘package:stats’:

    cov, smooth, var

g
library(corrplot)
G3;corrplot 0.95 loaded
g
library(rlang)
G3;
Adjuntando el paquete: ‘rlang’

gG3;The following objects are masked from ‘package:purrr’:

    %@%, flatten, flatten_chr, flatten_dbl, flatten_int, flatten_lgl,
    flatten_raw, invoke, splice

g
library(stringr)
library(beepr)
library(foreach)
library(permimp)

Define printall function

printall <- function(tibble) {
  print(tibble, width = Inf)
  }

Load geom_flat_violin plot

source("https://gist.githubusercontent.com/benmarwick/2a1bb0133ff568cbe28d/raw/fb53bd97121f7f9ce947837ef1a4c65a73bffb3f/geom_flat_violin.R")

Load previously created objects

# Define the folder path
folder_path <- here("objects", "10")

# List all .RData or .rda files in the folder
rdata_files <- list.files(folder_path, full.names = TRUE)

# Load each file
lapply(rdata_files, load, envir = .GlobalEnv)
[[1]]
[1] "predictions_rf0_S2"

[[2]]
[1] "predictions_rf1_S2"

[[3]]
[1] "predictions_rf2_S2"

[[4]]
[1] "predictions_rf3_S2"

[[5]]
[1] "predictions_rf4_S2"

[[6]]
[1] "predictions_rf5_S2"

[[7]]
[1] "rf0_S2"

[[8]]
[1] "rf1_S2"

[[9]]
[1] "rf2_S2"

[[10]]
[1] "rf3_S2"

[[11]]
[1] "rf4_S2"

[[12]]
[1] "rf5_S2"

[[13]]
[1] "roc_data0_S2"

[[14]]
[1] "roc_data1_S2"

[[15]]
[1] "roc_data2_S2"

[[16]]
[1] "roc_data3_S2"

[[17]]
[1] "roc_data4_S2"

[[18]]
[1] "roc_data5_S2"

[[19]]
[1] "varimp_rf0_S2"

Read data

data_validation<-read_tsv(here("data", "clean","final_RS_data_bands_S2_all.csv"))
Rows: 32661 Columns: 56
── Column specification ────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr  (7): EUNISa_1, EUNISa_1_descr, EUNISa_2, EUNISa_2_descr, biogeo, unit, Lctnmth
dbl (49): PlotObservationID, EVI_max, NDVI_max, SAVI_max, EVI_min, NDVI_min, SAVI_mi...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

No parsing issues!

Some data managenemt

TO-DO: Missing data checks

Do when all RS data is ready!

Distributions all bioregions

Indices

# Define a function to create histograms
plot_histogram <- function(data, x_var, x_label) {
  ggplot(data %>%
           dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
         aes(x = !!sym(x_var))) +
    geom_histogram(color = "black", fill = "white") +
    labs(x = x_label, y = "Frequency") +
    theme_bw()
}
# Define a function to create plots with violin + boxplot + points
distr_plot <- function(data, y_vars, y_labels) {
  for (i in seq_along(y_vars)) {
    y_var <- y_vars[[i]]
    y_label <- y_labels[[i]]
    
    p <- ggplot(data = data %>%
                  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
                aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
      geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
      geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
                 position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
      geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
      stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
      stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                     label = length(x)),
                   geom = "text", aes(label = ..label..), vjust = 0.5) +
      labs(y = y_label, x = "EUNIS level 1") +
      scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
      guides(fill = FALSE, color = FALSE) +
      theme_bw() + coord_flip()
    
    print(p)
  }
}

Ranges of min and max:

range(data_validation$NDVI_max, na.rm = T) # NDVI_max > 1 (slightly)
[1] 0.06064533 1.08505435
range(data_validation$NDMI_max, na.rm = T) # NDMI_max > 1 (slightly)
[1] -0.2202634  1.0960134
range(data_validation$NDWI_max, na.rm = T)
[1] -0.7291356  0.5942999
range(data_validation$SAVI_max, na.rm = T)
[1] 0.02386576 0.85981369
range(data_validation$EVI_max, na.rm = T) # EVI_max > 1 (slightly)
[1] 0.0475601 1.1938362
range(data_validation$NDVI_min, na.rm = T)
[1] -0.7058125  0.8423388
range(data_validation$NDMI_min, na.rm = T)
[1] -0.5601429  0.5758041
range(data_validation$NDWI_min, na.rm = T) # NDWI_min < -1 (slightly)
[1] -1.014830371 -0.008258131
range(data_validation$SAVI_min, na.rm = T)
[1] -0.5367437  0.6335289
range(data_validation$EVI_min, na.rm = T) # EVI_min < -1!
[1] -1.5674414  0.7891647
nrow(data_validation %>% dplyr::filter(NDVI_max > 1))
[1] 18
nrow(data_validation %>% dplyr::filter(NDMI_max > 1))
[1] 51
nrow(data_validation %>% dplyr::filter(EVI_max > 1))
[1] 9
nrow(data_validation %>% dplyr::filter(NDWI_min < -1))
[1] 4
nrow(data_validation %>% dplyr::filter(EVI_min < -1))
[1] 10

Histograms to check that max and min values are ok:

plot_histogram(data_validation, "NDVI_max", "NDVI max")

plot_histogram(data_validation, "NDMI_max", "NDMI max")

plot_histogram(data_validation, "NDWI_max", "NDWI max")

plot_histogram(data_validation, "SAVI_max", "SAVI max")

plot_histogram(data_validation, "EVI_max", "EVI max")

plot_histogram(data_validation, "NDVI_min", "NDVI min")

plot_histogram(data_validation, "NDMI_min", "NDMI min")

plot_histogram(data_validation, "NDWI_min", "NDWI min")

plot_histogram(data_validation, "SAVI_min", "SAVI min")

plot_histogram(data_validation, "EVI_min", "EVI min")

nrow(data_validation %>%
       dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
       dplyr::filter(EVI_max > 1 | EVI_max < -1))
[1] 9
data_validation %>%
       dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q"))%>%
  dplyr::filter(EVI_max > 1 | EVI_max < -1) %>%
  count(biogeo, unit)

Most EVI values are ok!

Distribution plots:

distr_plot(data_validation %>% dplyr::filter(EVI_min > -0.5),
           c("NDVI_max", "EVI_max", "SAVI_max", "NDMI_max", "NDWI_max",
             "NDVI_min", "EVI_min", "SAVI_min", "NDMI_min", "NDWI_min"),
           c("NDVI_max", "EVI_max", "SAVI_max", "NDMI_max", "NDWI_max",
             "NDVI_min", "EVI_min", "SAVI_min", "NDMI_min", "NDWI_min"))

CH

distr_plot(data_validation, "canopy_height", "Canopy height (m)")

Show habitats with CH categories

ggplot(data_validation %>%
         # Keep only forests, grasslands, shrublands and wetlands
         dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
         mutate(CH_cat =
                  factor(
                    case_when(canopy_height == 0 ~ "0 m",
                              canopy_height > 0 & canopy_height <= 1 ~ "0-1 m",
                              canopy_height > 1 & canopy_height <=2 ~ "1-2 m",
                              canopy_height > 2 & canopy_height <=5 ~ "2-5 m",
                              canopy_height > 5 & canopy_height <=8 ~ "5-8 m",
                              canopy_height > 8 ~ "> 8 m",
                              is.na(canopy_height) ~ NA_character_),
                    levels = c(
                      "0 m", "0-1 m", "1-2 m", "2-5 m", "5-8 m", "> 8 m"))),
       aes(x = EUNISa_1_descr, fill = CH_cat)) +
  geom_bar() + theme_bw() + coord_flip() +
  scale_y_continuous(labels = label_number()) +
  scale_fill_viridis_d(direction = -1) +
  labs(x = "EUNIS level 1", fill = "Canopy height") +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  theme(legend.position = c(0.8, 0.75),
        legend.direction = "vertical")

Stats per habitat type

data_validation %>%
  # Keep only forests, grasslands, shrublands and wetlands
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  group_by(EUNISa_1_descr) %>%
  summarise(across(canopy_height, list(
    mean = mean,
    median = median,
    sd = sd,
    min = min,
    max = max
    ), na.rm = TRUE))

Phenology

Only using NDVI- and SAVI-based values so far.

Maximum NDVI should be equal to value at peak?

nrow(data_validation %>% dplyr::filter(NDVI_pos_value  != NDVI_max))
[1] 590

Not sure why this happens, but in most cases the difference is small (< -0.1). Anyway, we should use only one of these two in the RF models.

plot_histogram(data_validation, "NDVI_sos_doy", "NDVI_sos_doy")

plot_histogram(data_validation, "NDVI_pos_doy", "NDVI_pos_doy")

plot_histogram(data_validation, "NDVI_eos_doy", "NDVI_eos_doy")

plot_histogram(data_validation, "EVI_sos_doy", "EVI_sos_doy")

plot_histogram(data_validation, "EVI_pos_doy", "EVI_pos_doy")

plot_histogram(data_validation, "EVI_eos_doy", "EVI_eos_doy")

plot_histogram(data_validation, "SAVI_sos_doy", "SAVI_sos_doy")

plot_histogram(data_validation, "SAVI_pos_doy", "SAVI_pos_doy")

plot_histogram(data_validation, "SAVI_eos_doy", "SAVI_eos_doy")

plot_histogram(data_validation, "NDVI_sos_value", "NDVI_sos_value")

plot_histogram(data_validation, "NDVI_pos_value", "NDVI_pos_value")

plot_histogram(data_validation, "NDVI_eos_value", "NDVI_eos_value")

plot_histogram(data_validation, "EVI_sos_value", "EVI_sos_value")

plot_histogram(data_validation, "EVI_pos_value", "EVI_pos_value")

plot_histogram(data_validation, "EVI_eos_value", "EVI_eos_value")

plot_histogram(data_validation, "NDVI_gsd", "NDVI_gsd")

plot_histogram(data_validation, "EVI_gsd", "EVI_gsd")

plot_histogram(data_validation, "SAVI_gsd", "SAVI_gsd")

plot_histogram(data_validation, "NDVI_diff_pos_sos_value",
               "NDVI_diff_pos_sos_value")

plot_histogram(data_validation, "EVI_diff_pos_sos_value",
               "EVI_diff_pos_sos_value")

plot_histogram(data_validation, "SAVI_diff_pos_sos_value",
               "SAVI_diff_pos_sos_value")

plot_histogram(data_validation, "NDVI_diff_pos_eos_value",
               "NDVI_diff_pos_eos_value")

plot_histogram(data_validation, "EVI_diff_pos_eos_value",
               "EVI_diff_pos_eos_value")

plot_histogram(data_validation, "SAVI_diff_pos_eos_value",
               "SAVI_diff_pos_eos_value")

plot_histogram(data_validation, "NDVI_diff_pos_sos_doy",
               "NDVI_diff_pos_sos_doy")

plot_histogram(data_validation, "EVI_diff_pos_sos_doy",
               "EVI_diff_pos_sos_doy")

plot_histogram(data_validation, "SAVI_diff_pos_sos_doy",
               "SAVI_diff_pos_sos_doy")

plot_histogram(data_validation, "NDVI_diff_eos_pos_doy",
               "NDVI_diff_eos_pos_doy")

plot_histogram(data_validation, "EVI_diff_eos_pos_doy", 
               "EVI_diff_eos_pos_doy")

plot_histogram(data_validation, "SAVI_diff_eos_pos_doy", 
               "SAVI_diff_eos_pos_doy")

plot_histogram(data_validation, "NDVI_auc", "NDVI_auc")

plot_histogram(data_validation, "EVI_auc", "EVI_auc")

plot_histogram(data_validation, "SAVI_auc", "SAVI_auc")

distr_plot(data_validation,
           c("NDVI_sos_value","NDVI_pos_value", "NDVI_eos_value",
             "EVI_sos_value","EVI_pos_value", "EVI_eos_value",
             "SAVI_sos_value", "SAVI_pos_value", "SAVI_eos_value",
             "NDVI_sos_doy","NDVI_pos_doy", "NDVI_eos_doy",
             "EVI_sos_doy","EVI_pos_doy", "EVI_eos_doy",
             "SAVI_sos_doy", "SAVI_pos_doy", "SAVI_eos_doy"),
           c("NDVI_sos_value","NDVI_pos_value", "NDVI_eos_value",
             "EVI_sos_value","EVI_pos_value", "EVI_eos_value",
             "SAVI_sos_Value", "SAVI_pos_value", "SAVI_eos_value",
             "NDVI_sos_doy","NDVI_pos_doy", "NDVI_eos_doy",
             "EVI_sos_doy","EVI_pos_doy", "EVI_eos_doy",
             "SAVI_sos_doy", "SAVI_pos_doy", "SAVI_eos_doy")
           )

distr_plot(data_validation,
           c("NDVI_gsd","EVI_gsd", "SAVI_gsd",
             "NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value",
             "SAVI_diff_pos_sos_value",
             "NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value",
             "SAVI_diff_pos_eos_value",
             "NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy",
             "SAVI_diff_pos_sos_doy",
             "NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy",
             "SAVI_diff_eos_pos_doy"),
           c("NDVI_gsd","EVI_gsd", "SAVI_gsd",
             "NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value",
             "SAVI_diff_pos_sos_Value",
             "NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value",
             "SAVI_diff_pos_eos_value",
             "NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy",
             "SAVI_diff_pos_sos_doy",
             "NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy",
             "SAVI_diff_eos_pos_doy")
           )

distr_plot(data_validation,
           c("NDVI_auc", "EVI_auc", "SAVI_auc"),
             c("NDVI_auc", "EVI_auc", "SAVI_auc"))

TBD: Distributions per bioregion

# Define a function to create plots with violin + boxplot + points
distr_plot_biogeo <- function(data, y_vars, y_labels) {
  plots <- list()
  
  for (i in seq_along(y_vars)) {
    y_var <- y_vars[[i]]
    y_label <- y_labels[[i]]
    
    p <- ggplot(data = data %>%
                  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
                aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
      geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
      geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
                 position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
      geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
      stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
      stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                     label = length(x)),
                   geom = "text", aes(label = ..label..), vjust = 0.5) +
      labs(y = y_label, x = "EUNISa_1_descr") +
      scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
      guides(fill = FALSE, color = FALSE) +
      theme_bw() + coord_flip() + facet_wrap(~ biogeo)
    
    plots[[y_var]] <- p
  }
  
  return(plots)
}

Indices

Distribution plots:

CH

distr_plot_biogeo(data_validation, "canopy_height", "Canopy height (m)")
$canopy_height

Phenology

Define functions for RF models

Function for fitting RF models

RF models fitted using the conditional inference version of random forest (first cforest in package party, now fastcforest in package moreparty). Suggested if the data are highly correlated. Cforest is more stable in deriving variable importance values in the presence of highly correlated variables, thus providing better accuracy in calculating variable importance (ref below).

Hothorn, T., Hornik, K. and Zeileis, A. (2006) Unbiased Recursive Portioning: A Conditional Inference Framework. Journal of Computational and Graphical Statistics, 15, 651- 674. http://dx.doi.org/10.1198/106186006X133933

Choose the hyperparameter mtry based on the square root of the number of predictor variables:

Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning: Data mining, inference, and prediction. Springer Science & Business Media.

Maybe TO_DO: We variated ntree from 50 to 800 in steps of 50, leaving mtry constant at 2. Tis parameter variation showed that ntree=500 was optimal, while higher ntree led to no further model improvement (Supplementary Fig. S10). Subsequently, the hyperparameter mtry was varied from 2 to 8 with constant ntree=500. Here, mtry=3 led to the best results in almost all cases (Supplementary Fig. S11). Consequently, we chose ntree=500 and mtry=3 for our main analysis across all study sites.

Define a function to run fastcforest models:

run_rf <- function(vars_RF, train_data, response_var, ntree = 500) 
  {
  
  # Detect and register available cores (leave one free)
  n_cores <- parallel::detectCores() - 1
  cl <- makeCluster(n_cores)
  registerDoParallel(cl)
  
  train_name <- deparse(substitute(train_data))
  
  # Export necessary variables to the cluster
  clusterExport(cl, varlist = c("vars_RF", train_name))
  
  # Set seed for reproducibility
  set.seed(123)
  
  # Measure execution time
  execution_time <- system.time({
    rf_model <- fastcforest(
      formula = reformulate(vars_RF, response = response_var),
      data = train_data,
      controls = party::cforest_control(
        mtry = round(sqrt(length(vars_RF))),
        ntree = ntree
      ),
      parallel = TRUE
    )
  })
  
  # Stop the cluster
  stopCluster(cl)
  
  # Return both the model and execution time
  list(model = rf_model, time = execution_time)
}

Function to compute variable importance

compute_varimp <- function(model, nperm = 100, 
                                   n_cores = parallel::detectCores() - 1) {
  # Set up parallel backend
  cl <- makeCluster(n_cores)
  registerDoParallel(cl)
  
  # Measure execution time
  execution_time <- system.time({
    varimp_list <- foreach(i = 1:nperm, .combine = '+', 
                           .packages = "party") %dopar% {
      varimp(model, conditional = FALSE, nperm = 1)
    }
  })
  
  stopCluster(cl)
  
  # Average the results
  varimp_avg <- varimp_list / nperm
  
  return(list(varimp = varimp_avg, time = execution_time))
}

Using permimp() en permimp package: https://cran.r-project.org/web/packages/permimp/vignettes/permimp-package.html#fn1

compute_varimp <- function(model, nperm = 100) {

  # Measure execution time
  execution_time <- system.time({
    varimp_result <- permimp(model, conditional = FALSE, progressBar = TRUE)
  })

  return(list(varimp = varimp_result, time = execution_time))
}

Function to compute CONDITIONAL variable importance

compute_varimp_cond <- function(model, nperm = 100) {

  # Measure execution time
  execution_time <- system.time({
    varimp_result <- permimp(model, conditional = TRUE, progressBar = TRUE)
  })

  return(list(varimp = varimp_result, time = execution_time))
}

Function to compute ROC (level 1)

compute_roc_level1 <- function(model, test_data) {
  # Measure execution time
  execution_time <- system.time({
    # Step 1: Predict probabilities
    probabilities <- predict(model, newdata = test_data, type = "prob")
    
    # Step 2: Convert list of matrices to a proper data frame
    prob_matrix <- t(sapply(probabilities, as.vector))
    prob_df <- as.data.frame(prob_matrix)
    colnames(prob_df) <- c("Q", "R", "S", "T")
    
    # Step 3: Prepare actual class labels
    actual <- factor(test_data$EUNISa_1, levels = c("Q", "R", "S", "T"))
    
    # Step 4: Binarize actual labels
    actual_bin <- model.matrix(~ actual - 1)
    colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
    
    # Step 5: Compute ROC data for each class
    roc_data <- lapply(levels(actual), function(class) {
      roc_obj <- roc(actual_bin[, class], prob_df[[class]])
      auc_val <- round(auc(roc_obj), 3)
      data.frame(
        FPR = rev(roc_obj$specificities),
        TPR = rev(roc_obj$sensitivities),
        Class = paste0(class, " (AUC = ", auc_val, ")")
      )
    }) %>% bind_rows()
  })
  
  # Return both ROC data and execution time
  return(list(roc = roc_data, time = execution_time))
}

Function to compute ROC (level 2)

compute_roc_level2 <- function(model, test_data) {
  # Measure execution time
  execution_time <- system.time({
    # Step 1: Predict probabilities
    probabilities <- predict(model, newdata = test_data, type = "prob")
    
    # Step 2: Convert list of matrices to a proper data frame
    prob_matrix <- t(sapply(probabilities, as.vector))
    prob_df <- as.data.frame(prob_matrix)
    colnames(prob_df) <- c("Q1", "Q2", "Q4", "Q5", "R1", "R2", "R3", "R4", "R5",
                           "R6", "S3", "S4", "T1", "T3")
    
    # Step 3: Prepare actual class labels
    actual <- factor(test_data$EUNISa_2, 
                     levels = c("Q1", "Q2", "Q4", "Q5", "R1", "R2", "R3", "R4",
                                "R5", "R6", "S3", "S4", "T1", "T3"))
    
    # Step 4: Binarize actual labels
    actual_bin <- model.matrix(~ actual - 1)
    colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
    
    # Step 5: Compute ROC data for each class
    roc_data <- lapply(levels(actual), function(class) {
      roc_obj <- roc(actual_bin[, class], prob_df[[class]])
      auc_val <- round(auc(roc_obj), 3)
      data.frame(
        FPR = rev(roc_obj$specificities),
        TPR = rev(roc_obj$sensitivities),
        Class = paste0(class, " (AUC = ", auc_val, ")")
      )
    }) %>% bind_rows()
  })
  
  # Return both ROC data and execution time
  return(list(roc = roc_data, time = execution_time))
}

Define list of predictor vars

vars_RF <- c(
  # Min values of all indices
  "NDVI_min", "EVI_min", "NDMI_min", "NDWI_min", "SAVI_min",
  # Max values of NDMI and NDWI
  "NDMI_max", "NDWI_max",
  # AUC of NDVI, EVI and SAVI
  "NDVI_auc", "EVI_auc", "SAVI_auc",
  # Values of NDVI, EVI and SAVI at sos, pos and eos
  "NDVI_sos_value", "NDVI_pos_value", "NDVI_eos_value",
  "EVI_sos_value", "EVI_pos_value", "EVI_eos_value", 
  "SAVI_sos_value", "SAVI_pos_value", "SAVI_eos_value",
  # Differences pos-sos in value and doy
  "NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value", "SAVI_diff_pos_sos_value",
  "NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy", "SAVI_diff_pos_sos_doy",
  # Differences pos-eos in value and doy
  "NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value","SAVI_diff_pos_eos_value",
  "NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy", "SAVI_diff_eos_pos_doy",
  # Growing season duration
  "NDVI_gsd", "EVI_gsd", "SAVI_gsd",
  # Canopy height
  "canopy_height")

Correlation

filtered_data0 <- data_validation %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  # Remove all rows with wrong values of indices (not between -1 and 1)
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
  dplyr::filter(NDVI_max <= 1) %>%
  dplyr::filter(NDMI_max <= 1) %>%
  dplyr::filter(NDWI_min >= -1) %>%
  # Remove rows with missing values in predictors
  dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
  # Keep only rows with differences > 0
  dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
  # Select only variables needed
  select(PlotObservationID, EUNISa_1, all_of(vars_RF))

Correlation of all variables to be included in RF models:

corrplot(filtered_data0 %>% 
           dplyr::select(all_of(vars_RF)) %>%
           cor(use = "pairwise.complete.obs"),
         method = "color", type = "upper", tl.col = "black", tl.srt = 45)

# Compute correlation matrix
cor_matrix <- filtered_data0 %>%
  dplyr::select(all_of(vars_RF)) %>%
  cor(use = "pairwise.complete.obs")

# Get the upper triangle of the matrix without the diagonal
cor_matrix[lower.tri(cor_matrix, diag = TRUE)] <- NA

# Find variable pairs with correlation > 0.7 or < -0.7
high_corr_pairs <- which(abs(cor_matrix) > 0.7, arr.ind = TRUE)

# Create a data frame of the results
cor_pairs_df <- data.frame(
  Var1 = rownames(cor_matrix)[high_corr_pairs[, 1]],
  Var2 = colnames(cor_matrix)[high_corr_pairs[, 2]],
  Correlation = cor_matrix[high_corr_pairs]
)

# View the result
print(cor_pairs_df)

Define list of uncorrelated predictor vars

vars_RF_uncorr <- c(
  # Min values
  "NDMI_min", "NDWI_min", "SAVI_min",
  # Max value of NDMI
  "NDMI_max",
  # AUC
  "SAVI_auc",
  # Values of SAVI at sos, pos and eos
  "SAVI_sos_value", "SAVI_pos_value", "SAVI_eos_value",
  # Differences pos-sos in value and doy
  "SAVI_diff_pos_sos_value", "SAVI_diff_pos_sos_doy",
  # Differences pos-eos in value and doy
  "SAVI_diff_pos_eos_value", "SAVI_diff_eos_pos_doy",
  # Canopy height
  "canopy_height")

RF models with no previous validation

0: RF with all points

Split filtered_data0 into training and test data sets.

set.seed(123) 
train_indices0 <- sample(1:nrow(filtered_data0), 0.7 * nrow(filtered_data0))
train_data0 <- filtered_data0[train_indices0, ]
test_data0 <- filtered_data0[-train_indices0, ]

Number of points per category for filtered data:

filtered_data0 %>% count(EUNISa_1)

Confusion matrix:

confusionMatrix(predictions_rf0_S2, test_data0$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  963  295  140   16
         R  795 4214  448  124
         S  216  187  869   42
         T   25   81   52  659

Overall Statistics
                                          
               Accuracy : 0.7347          
                 95% CI : (0.7255, 0.7438)
    No Information Rate : 0.5234          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5679          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.4817   0.8821  0.57588  0.78359
Specificity            0.9367   0.6857  0.94158  0.98093
Pos Pred Value         0.6810   0.7551  0.66134  0.80661
Neg Pred Value         0.8657   0.8412  0.91807  0.97810
Prevalence             0.2190   0.5234  0.16535  0.09215
Detection Rate         0.1055   0.4618  0.09522  0.07221
Detection Prevalence   0.1549   0.6115  0.14398  0.08952
Balanced Accuracy      0.7092   0.7839  0.75873  0.88226

Variable Importance Plot

plot(varimp_rf0_S2$varimp, margin = c(9, 3, 2, 2))

ROC curves:

roc0 <- ggplot(roc_data0_S2$roc, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
Warning :Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_]8;;ide:run:warnings()warnings()]8;;` to see where this warning was generated.
roc0

1: RF with all GPS points (diff or not)

filtered_data1 <- data_validation %>%
  # Select only GPS points
  dplyr::filter(Lctnmth == "Location with GPS" |
                  Lctnmth == "Location with differential GPS") %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  # Remove all rows with wrong values of indices (not between -1 and 1)
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
  dplyr::filter(NDVI_max <= 1) %>%
  dplyr::filter(NDMI_max <= 1) %>%
  dplyr::filter(NDWI_min >= -1) %>%
  # Remove rows with missing values in predictors
  dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
  # Keep only rows with differences > 0
  dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
  # Select only variables needed
  select(PlotObservationID, EUNISa_1, all_of(vars_RF))

Split into training and test data sets.

set.seed(123)
train_indices1 <- sample(1:nrow(filtered_data1), 0.7 * nrow(filtered_data1))
train_data1 <- filtered_data1[train_indices1, ]
test_data1 <- filtered_data1[-train_indices1, ]

Number of points per category for filtered data:

filtered_data1 %>% count(EUNISa_1)

Confusion matrix:

confusionMatrix(predictions_rf1_S2, test_data1$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q 1077  341  219   13
         R  537 2535  336   69
         S  201  158  835   15
         T   16   24   15   90

Overall Statistics
                                          
               Accuracy : 0.7             
                 95% CI : (0.6887, 0.7112)
    No Information Rate : 0.4718          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5268          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.5882   0.8290   0.5943  0.48128
Specificity            0.8768   0.7248   0.9263  0.99126
Pos Pred Value         0.6527   0.7291   0.6907  0.62069
Neg Pred Value         0.8439   0.8259   0.8919  0.98469
Prevalence             0.2825   0.4718   0.2168  0.02885
Detection Rate         0.1662   0.3911   0.1288  0.01389
Detection Prevalence   0.2546   0.5365   0.1865  0.02237
Balanced Accuracy      0.7325   0.7769   0.7603  0.73627

Variable Importance Plot

plot(varimp_rf1_S2$varimp, margin = c(9, 3, 2, 2))
plot(varimp_rf1_cond_S2$varimp, margin = c(9, 3, 2, 2))

ROC curves:

roc1 <- ggplot(roc_data1_S2$roc, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc1

Rough validation

Define a set of rules for a first validation of ALL ReSurvey data (“Expert-based” rules). Not very ambitious, only taking out observations that are clearly wrong on the basis of indicator values.

Create column for first validation based on different indicators, where “wrong” is noted when the validation rule is not met.

Define rules:

data_validation <-
  data_validation %>%
  mutate(
    valid_1_NDWI = case_when(
      # Points that are basically water
      NDWI_max > 0.3 ~ "wrong",
      TRUE ~ NA_character_),
    valid_1_CH = case_when(
      # R & Q points with high CH
      EUNISa_1 %in% c("R", "Q") & canopy_height > 2 ~ "wrong",
      # T points with low CH
      EUNISa_1 == "T" & canopy_height < 3 ~ "wrong",
      # S points with high CH
      EUNISa_1 == "S" & canopy_height > 3 ~ "wrong",
      TRUE ~ NA_character_),
    # No rules for NDVI, we will take out observations based on distributions
    # Count how many validation rules are not met
    valid_1_count = rowSums(across(c(valid_1_NDWI, valid_1_CH), ~ . == "wrong"),
                            na.rm = TRUE),
    # Points where at least 1 rule not met
    valid_1 = if_else(valid_1_count > 0, "At least 1 rule broken",
                      "No rules broken so far")
    )

Plots rough validation

ggplot(data_validation%>%
         mutate(rules_broken = case_when(
           valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
           valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
           TRUE ~ NA_character_
         )), 
       aes(x = valid_1_count, fill = rules_broken)) +
  geom_bar() + labs(x = "Number of broken rules")

Proportion of observations not validated (so far):

nrow(data_validation %>% dplyr::filter(valid_1_count > 0))/
  nrow(data_validation)
[1] 0.0866783

But be aware that there are still MANY missing RS data.

ggplot(data_validation %>%
         mutate(diff_GPS = if_else(
           Lctnmth != "Location with differential GPS" |
             is.na(Lctnmth), "no", "yes")), 
       aes(x = diff_GPS, fill = valid_1)) +
  geom_bar() + labs(x = "Differential GPS")

ggplot(data_validation %>%
         mutate(GPS = case_when(
           Lctnmth == "Location with differential GPS" ~ "yes",
           Lctnmth == "Location with GPS" ~ "yes",
           is.na(Lctnmth) ~ "no",
           TRUE ~ "no"
         )), 
       aes(x = GPS, fill = valid_1)) +
  geom_bar() + labs(x = "GPS")

RF models after rough validation

2: RF with all points after rough validation

filtered_data2 <- data_validation %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  # Remove all rows with wrong values of indices (not between -1 and 1)
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
  dplyr::filter(NDVI_max <= 1) %>%
  dplyr::filter(NDMI_max <= 1) %>%
  dplyr::filter(NDWI_min >= -1) %>%
  # Remove rows with missing values in predictors
  dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
  # Keep only rows with differences > 0
  dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
  # Filter out points that have not passed the rough validation
  filter(valid_1 == "No rules broken so far") %>%
  # Select only variables needed
  select(PlotObservationID, EUNISa_1, all_of(vars_RF)) 

Split into training and test data sets.

set.seed(123)
train_indices2 <- sample(1:nrow(filtered_data2), 0.7 * nrow(filtered_data2))
train_data2 <- filtered_data2[train_indices2, ]
test_data2 <- filtered_data2[-train_indices2, ]

Number of points per category for filtered data:

filtered_data2 %>% count(EUNISa_1)

Confusion matrix:

confusionMatrix(predictions_rf2_S2, test_data2$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  936  230  144    1
         R  707 4007  399    4
         S  227  142  818    2
         T    0    0    2  720

Overall Statistics
                                          
               Accuracy : 0.7772          
                 95% CI : (0.7681, 0.7861)
    No Information Rate : 0.5251          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.6357          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.5005   0.9150  0.60015  0.99037
Specificity            0.9420   0.7197  0.94682  0.99974
Pos Pred Value         0.7140   0.7831  0.68797  0.99723
Neg Pred Value         0.8671   0.8845  0.92378  0.99908
Prevalence             0.2242   0.5251  0.16345  0.08718
Detection Rate         0.1122   0.4805  0.09809  0.08634
Detection Prevalence   0.1572   0.6136  0.14258  0.08658
Balanced Accuracy      0.7213   0.8174  0.77348  0.99505

Variable Importance Plot

plot(varimp_rf2_S2$varimp, margin = c(9, 3, 2, 2))
plot(varimp_rf2_cond_S2$varimp, margin = c(9, 3, 2, 2))

ROC curves:

roc2 <- ggplot(roc_data2_S2$roc, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc2

3: RF with GPS points after rough validation

filtered_data3 <- data_validation %>%
  # Select only GPS points
  dplyr::filter(Lctnmth == "Location with GPS" |
                  Lctnmth == "Location with differential GPS") %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  # Remove all rows with wrong values of indices (not between -1 and 1)
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
  dplyr::filter(NDVI_max <= 1) %>%
  dplyr::filter(NDMI_max <= 1) %>%
  dplyr::filter(NDWI_min >= -1) %>%
  # Remove rows with missing values in predictors
  dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
  # Keep only rows with differences > 0
  dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
  # Filter out points that have not passed the rough validation
  filter(valid_1 == "No rules broken so far") %>%
  # Select only variables needed
  select(PlotObservationID, EUNISa_1, all_of(vars_RF))

Split into training and test data sets.

set.seed(123)
train_indices3 <- sample(1:nrow(filtered_data3), 0.7 * nrow(filtered_data3))
train_data3 <- filtered_data3[train_indices3, ]
test_data3 <- filtered_data3[-train_indices3, ]

Number of points per category for filtered data:

filtered_data3 %>% count(EUNISa_1)

Confusion matrix:

confusionMatrix(predictions_rf3_S2, test_data3$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q 1004  298  156    1
         R  574 2409  275    3
         S  188  140  757    1
         T    0    0    1  133

Overall Statistics
                                          
               Accuracy : 0.7244          
                 95% CI : (0.7129, 0.7357)
    No Information Rate : 0.4793          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5603          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.5685   0.8462   0.6367  0.96377
Specificity            0.8910   0.7245   0.9308  0.99983
Pos Pred Value         0.6881   0.7387   0.6971  0.99254
Neg Pred Value         0.8299   0.8365   0.9110  0.99914
Prevalence             0.2973   0.4793   0.2002  0.02323
Detection Rate         0.1690   0.4056   0.1274  0.02239
Detection Prevalence   0.2456   0.5490   0.1828  0.02256
Balanced Accuracy      0.7298   0.7853   0.7837  0.98180

Variable Importance Plot

plot(varimp_rf3_S2$varimp, margin = c(9, 3, 2, 2))
plot(varimp_rf3_cond_S2$varimp, margin = c(9, 3, 2, 2))

ROC curves:

roc3 <- ggplot(roc_data3_S2$roc, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc3

4: RF (level 2) with GPS points after rough validation

filtered_data4 <- data_validation %>%
  # Select only GPS points
  dplyr::filter(Lctnmth == "Location with GPS" |
                  Lctnmth == "Location with differential GPS") %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1), EUNISa_2 = as.factor(EUNISa_2)) %>%
  # Remove all rows with wrong values of indices (not between -1 and 1)
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
  dplyr::filter(NDVI_max <= 1) %>%
  dplyr::filter(NDMI_max <= 1) %>%
  dplyr::filter(NDWI_min >= -1) %>%
  # Remove rows with missing values in predictors
  dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
  # Keep only rows with differences > 0
  dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
  # Filter out points that have not passed the rough validation
  filter(valid_1 == "No rules broken so far") %>%
  # Filter out points where EUNISa_2 is NA
  filter(!is.na(EUNISa_2)) %>%
  # Select only variables needed
  select(PlotObservationID, EUNISa_1, EUNISa_2, all_of(vars_RF))

Number of points per category for filtered data:

filtered_data4 %>% count(EUNISa_2)
filtered_data4 <- filtered_data4 %>%
  # Keep only EUNISa_2 categories where there are at least 50 points
  filter(EUNISa_2 %in%
           (filtered_data4 %>% count(EUNISa_2) %>% filter(n >= 50))$EUNISa_2) %>%
  # Drop unused levels of EUNISa_2
  mutate(EUNISa_2 = droplevels(EUNISa_2))

Number of points per category for filtered data:

filtered_data4 %>% count(EUNISa_2)

Split into training and test data sets.

set.seed(123)
train_indices4 <- sample(1:nrow(filtered_data4), 0.7 * nrow(filtered_data4))
train_data4 <- filtered_data4[train_indices4, ]
test_data4 <- filtered_data4[-train_indices4, ]

Confusion matrix:

confusionMatrix(predictions_rf4_S2, test_data4$EUNISa_2)
Confusion Matrix and Statistics

          Reference
Prediction   Q1   Q2   Q4   Q5   R1   R2   R3   R4   R5   R6   S3   S4   T1   T3
        Q1   66    3    9    0    7    1    7    0    0    1    2    5    0    0
        Q2   17   50   14    0    3    0    3    0    1    2    0    5    0    0
        Q4   51   89  577    2  123   11  131    0    2    2   12   54    1    0
        Q5    0    0    0   16    0    0    0    0    0    0    0    0    0    0
        R1   73  131  250   10 1301  119  132   13   10   23   53  175    1    0
        R2    2    3    3    1   17   61   12    6    5    0    1    2    0    0
        R3    1    1    8    7    8   11  110    0   18    0    0    4    0    0
        R4    0    0    0    0    1    0    0    3    0    0    0    0    0    0
        R5    0    0    0    0    0    3    8    0    5    0    0    0    0    0
        R6    0    1    0    0    0    0    0    0    0    1    0    0    0    0
        S3    0    0    0    0    0    0    0    0    0    0    1    0    0    0
        S4  139  118   69    3  119    9   37    0    2    7   41  787    1    0
        T1    0    0    0    0    0    0    0    0    0    0    2    0   91    9
        T3    0    0    0    0    0    0    0    0    0    0    0    0    5    6

Overall Statistics
                                         
               Accuracy : 0.5794         
                 95% CI : (0.566, 0.5928)
    No Information Rate : 0.2975         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.4618         
                                         
 Mcnemar's Test P-Value : NA             

Statistics by Class:

                     Class: Q1 Class: Q2 Class: Q4 Class: Q5 Class: R1 Class: R2
Sensitivity            0.18911  0.126263    0.6204  0.410256    0.8239   0.28372
Specificity            0.99294  0.990837    0.8908  1.000000    0.7344   0.98979
Pos Pred Value         0.65347  0.526316    0.5469  1.000000    0.5679   0.53982
Neg Pred Value         0.94564  0.933615    0.9170  0.995653    0.9078   0.97035
Prevalence             0.06576  0.074618    0.1752  0.007349    0.2975   0.04051
Detection Rate         0.01244  0.009422    0.1087  0.003015    0.2451   0.01149
Detection Prevalence   0.01903  0.017901    0.1988  0.003015    0.4317   0.02129
Balanced Accuracy      0.59103  0.558550    0.7556  0.705128    0.7792   0.63675
                     Class: R3 Class: R4 Class: R5 Class: R6 Class: S3 Class: S4
Sensitivity            0.25000 0.1363636 0.1162791 0.0277778 0.0089286    0.7626
Specificity            0.98808 0.9998108 0.9979103 0.9998103 1.0000000    0.8725
Pos Pred Value         0.65476 0.7500000 0.3125000 0.5000000 1.0000000    0.5908
Neg Pred Value         0.93579 0.9964171 0.9928180 0.9934025 0.9790803    0.9384
Prevalence             0.08291 0.0041455 0.0081025 0.0067835 0.0211042    0.1945
Detection Rate         0.02073 0.0005653 0.0009422 0.0001884 0.0001884    0.1483
Detection Prevalence   0.03166 0.0007537 0.0030149 0.0003769 0.0001884    0.2510
Balanced Accuracy      0.61904 0.5680872 0.5570947 0.5137940 0.5044643    0.8176
                     Class: T1 Class: T3
Sensitivity            0.91919  0.400000
Specificity            0.99789  0.999055
Pos Pred Value         0.89216  0.545455
Neg Pred Value         0.99846  0.998301
Prevalence             0.01865  0.002826
Detection Rate         0.01715  0.001131
Detection Prevalence   0.01922  0.002073
Balanced Accuracy      0.95854  0.699528

Variable Importance Plot

plot(varimp_rf4_S2$varimp, margin = c(9, 3, 2, 2))
plot(varimp_rf4_cond_S2$varimp, margin = c(9, 3, 2, 2))

ROC curves:

roc4 <- ggplot(roc_data4_S2$roc, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc4

5: RF with GPS points after rough validation, uncorrelated predictors

filtered_data5 <- data_validation %>%
  # Select only GPS points
  dplyr::filter(Lctnmth == "Location with GPS" |
                  Lctnmth == "Location with differential GPS") %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  # Remove all rows with wrong values of indices (not between -1 and 1)
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
  dplyr::filter(NDVI_max <= 1) %>%
  dplyr::filter(NDMI_max <= 1) %>%
  dplyr::filter(NDWI_min >= -1) %>%
  # Remove rows with missing values in predictors
  dplyr::filter(if_all(all_of(vars_RF_uncorr), ~ !is.na(.))) %>%
  # Keep only rows with differences > 0
  dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
  # Filter out points that have not passed the rough validation
  dplyr::filter(valid_1 == "No rules broken so far") %>%
  # Select only variables needed
  dplyr::select(PlotObservationID, EUNISa_1, all_of(vars_RF_uncorr))

Split into training and test data sets.

set.seed(123)
train_indices5 <- sample(1:nrow(filtered_data5), 0.7 * nrow(filtered_data5))
train_data5 <- filtered_data3[train_indices5, ]
test_data5 <- filtered_data3[-train_indices5, ]

Number of points per category for filtered data:

filtered_data5 %>% count(EUNISa_1)

Confusion matrix:

confusionMatrix(predictions_rf5_S2, test_data5$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  954  293  161    0
         R  586 2382  267    3
         S  226  172  761    2
         T    0    0    0  133

Overall Statistics
                                          
               Accuracy : 0.7121          
                 95% CI : (0.7004, 0.7236)
    No Information Rate : 0.4793          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.542           
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.5402   0.8367   0.6400  0.96377
Specificity            0.8912   0.7232   0.9158  1.00000
Pos Pred Value         0.6776   0.7356   0.6555  1.00000
Neg Pred Value         0.8208   0.8279   0.9104  0.99914
Prevalence             0.2973   0.4793   0.2002  0.02323
Detection Rate         0.1606   0.4010   0.1281  0.02239
Detection Prevalence   0.2370   0.5451   0.1955  0.02239
Balanced Accuracy      0.7157   0.7800   0.7779  0.98188

Variable Importance Plot

plot(varimp_rf5_S2$varimp, margin = c(9, 3, 2, 2))
plot(varimp_rf5_cond_S2$varimp, margin = c(9, 3, 2, 2))

ROC curves:

roc5 <- ggplot(roc_data5_S2$roc, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc5

RF mmodels after training data refinement

6: GPS points, within 10-90th percentile

Refinement based on the variables: SAVI_pos_value, NDWI_min, NDMI_min, NDMI_max (later check variable importances well!).

Distribution plots:

distr_plot_percentiles <- function(data, y_vars, y_labels) {
  for (i in seq_along(y_vars)) {
    y_var <- y_vars[[i]]
    y_label <- y_labels[[i]]
    
    # Calculate percentiles per EUNISa_1 group
    percentiles <- data %>%
      group_by(EUNISa_1) %>%
      summarise(
        p10 = quantile(.data[[y_var]], 0.1, na.rm = TRUE),
        p90 = quantile(.data[[y_var]], 0.9, na.rm = TRUE),
        .groups = "drop"
      )
    
    # Join percentiles back to data
    data_flagged <- data %>%
      left_join(percentiles, by = "EUNISa_1") %>%
      mutate(outlier_flag = case_when(
        .data[[y_var]] < p10 ~ "low",
        .data[[y_var]] > p90 ~ "high",
        TRUE ~ "mid"
      ))
    
    # Filter and plot
    p <- ggplot(data = data_flagged %>%
                  filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
                aes(x = EUNISa_1_descr, y = .data[[y_var]])) +
      geom_flat_violin(aes(fill = EUNISa_1_descr),
                       position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
      geom_point(aes(color = ifelse(outlier_flag == "mid",
                                    EUNISa_1_descr, "grey")),
                 position = position_jitter(width = 0.15), size = 1,
                 alpha = 0.6) +
      geom_boxplot(aes(fill = EUNISa_1_descr), width = 0.2, outlier.shape = NA,
                   alpha = 0.5) +
      stat_summary(fun = mean, geom = "point", shape = 20, size = 1) +
      stat_summary(fun.data = function(x) data.frame(y = max(x, na.rm = TRUE) +
                                                       0.1, label = length(x)),
                   geom = "text", aes(label = ..label..), vjust = 0.5) +
      labs(y = y_label, x = "EUNIS level 1") +
      scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
      guides(fill = FALSE, color = FALSE) +
      theme_bw() + coord_flip()
    
    print(p)
  }
}
distr_plot_percentiles(data_validation %>%
                         # Get all points after rough validation
             filter(PlotObservationID %in% filtered_data3$PlotObservationID),
           c("canopy_height", "SAVI_pos_value", "NDWI_min", "NDMI_min", 
             "NDMI_max"),
           c("canopy_height", "SAVI_pos_value", "NDWI_min", "NDMI_min", 
             "NDMI_max"))

So far not using canopy_height for refinement.

Calculate percentiles:

percentiles6 <- data_validation %>%
  # Get GPS points after rough validation
  filter(PlotObservationID %in% filtered_data3$PlotObservationID) %>%
  group_by(EUNISa_1) %>%
  summarize(
    percentile_10_SAVI_pos_value = quantile(SAVI_pos_value, 
                                            probs = 0.10, na.rm = T),
    percentile_20_SAVI_pos_value = quantile(SAVI_pos_value, 
                                            probs = 0.20, na.rm = T),
    percentile_80_SAVI_pos_value = quantile(SAVI_pos_value, 
                                            probs = 0.80, na.rm = T),
    percentile_90_SAVI_pos_value = quantile(SAVI_pos_value,
                                            probs = 0.90, na.rm = T),
    percentile_10_NDWI_min = quantile(NDWI_min, 
                                            probs = 0.10, na.rm = T),
    percentile_20_NDWI_min = quantile(NDWI_min, 
                                            probs = 0.20, na.rm = T),
    percentile_80_NDWI_min = quantile(NDWI_min, 
                                            probs = 0.80, na.rm = T),
    percentile_90_NDWI_min = quantile(NDWI_min,
                                            probs = 0.90, na.rm = T),
    percentile_10_NDMI_min = quantile(NDMI_min, 
                                            probs = 0.10, na.rm = T),
    percentile_20_NDMI_min = quantile(NDMI_min, 
                                            probs = 0.20, na.rm = T),
    percentile_80_NDMI_min = quantile(NDMI_min, 
                                            probs = 0.80, na.rm = T),
    percentile_90_NDMI_min = quantile(NDMI_min,
                                            probs = 0.90, na.rm = T),
    percentile_10_NDMI_max = quantile(NDMI_max, 
                                            probs = 0.10, na.rm = T),
    percentile_20_NDMI_max = quantile(NDMI_max, 
                                            probs = 0.20, na.rm = T),
    percentile_80_NDMI_max = quantile(NDMI_max, 
                                            probs = 0.80, na.rm = T),
    percentile_90_NDMI_max = quantile(NDMI_max,
                                            probs = 0.90, na.rm = T)
            )
filtered_data6 <- data_validation %>%
  # Get GPS points after rough validation
  filter(PlotObservationID %in% filtered_data3$PlotObservationID) %>%
  select(PlotObservationID, EUNISa_1, all_of(vars_RF)) %>%
  left_join(percentiles6, by = "EUNISa_1") %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(
    (SAVI_pos_value >= percentile_10_SAVI_pos_value &
       SAVI_pos_value <= percentile_90_SAVI_pos_value) &
      (NDWI_min >= percentile_10_NDWI_min &
         NDWI_min <= percentile_90_NDWI_min) &
      (NDMI_min >= percentile_10_NDMI_min &
         NDMI_min <= percentile_90_NDMI_min) &
      (NDMI_max >= percentile_10_NDMI_max &
         NDMI_max <= percentile_90_NDMI_max)
    )

Split into training and test data sets.

set.seed(123)
train_indices6 <- sample(1:nrow(filtered_data6), 0.7 * nrow(filtered_data6))
train_data6 <- filtered_data6[train_indices6, ]
test_data6 <- filtered_data6[-train_indices6, ]

Number of points per category for filtered data:

filtered_data6 %>% count(EUNISa_1)

Confusion matrix:

confusionMatrix(predictions_rf6_S2, test_data6$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  474  175   56    0
         R  238 1188   98    0
         S   70   65  400    2
         T    0    0    0   66

Overall Statistics
                                          
               Accuracy : 0.7514          
                 95% CI : (0.7351, 0.7672)
    No Information Rate : 0.5042          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.6005          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.6061   0.8319   0.7220  0.97059
Specificity            0.8873   0.7607   0.9399  1.00000
Pos Pred Value         0.6723   0.7795   0.7449  1.00000
Neg Pred Value         0.8552   0.8165   0.9329  0.99928
Prevalence             0.2761   0.5042   0.1956  0.02401
Detection Rate         0.1674   0.4195   0.1412  0.02331
Detection Prevalence   0.2489   0.5381   0.1896  0.02331
Balanced Accuracy      0.7467   0.7963   0.8309  0.98529

Variable Importance Plot

plot(varimp_rf6_S2$varimp, margin = c(9, 3, 2, 2))
plot(varimp_rf6_cond_S2$varimp, margin = c(9, 3, 2, 2))

ROC curves:

roc6 <- ggplot(roc_data6_S2$roc, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc6

7: GPS points, within 20-80th percentile

filtered_data7 <- data_validation %>%
  # Get GPS points after rough validation
  filter(PlotObservationID %in% filtered_data3$PlotObservationID) %>%
  select(PlotObservationID, EUNISa_1, all_of(vars_RF)) %>%
  left_join(percentiles6, by = "EUNISa_1") %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(
    (SAVI_pos_value >= percentile_20_SAVI_pos_value &
       SAVI_pos_value <= percentile_80_SAVI_pos_value) &
      (NDWI_min >= percentile_20_NDWI_min &
         NDWI_min <= percentile_80_NDWI_min) &
      (NDMI_min >= percentile_20_NDMI_min &
         NDMI_min <= percentile_80_NDMI_min) &
      (NDMI_max >= percentile_20_NDMI_max &
         NDMI_max <= percentile_80_NDMI_max)
    )

Split into training and test data sets.

set.seed(123)
train_indices7 <- sample(1:nrow(filtered_data7), 0.7 * nrow(filtered_data7))
train_data7 <- filtered_data7[train_indices7, ]
test_data7 <- filtered_data7[-train_indices7, ]

Number of points per category for filtered data:

filtered_data7 %>% count(EUNISa_1)

Confusion matrix:

confusionMatrix(predictions_rf7_S2, test_data7$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction   Q   R   S   T
         Q 222  60  13   0
         R  97 375  15   0
         S  20  12 178   0
         T   0   0   0  25

Overall Statistics
                                          
               Accuracy : 0.7866          
                 95% CI : (0.7601, 0.8114)
    No Information Rate : 0.4395          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.6719          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.6549   0.8389   0.8641  1.00000
Specificity            0.8923   0.8035   0.9605  1.00000
Pos Pred Value         0.7525   0.7700   0.8476  1.00000
Neg Pred Value         0.8380   0.8642   0.9653  1.00000
Prevalence             0.3333   0.4395   0.2026  0.02458
Detection Rate         0.2183   0.3687   0.1750  0.02458
Detection Prevalence   0.2901   0.4789   0.2065  0.02458
Balanced Accuracy      0.7736   0.8212   0.9123  1.00000

Variable Importance Plot

plot(varimp_rf7_S2$varimp, margin = c(9, 3, 2, 2))
plot(varimp_rf7_cond_S2$varimp, margin = c(9, 3, 2, 2))

ROC curves:

roc7 <- ggplot(roc_data7_S2$roc, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc6

OLD from here

Maps

Points GPS

# Load world boundaries
world <- ne_countries(scale = "medium", returnclass = "sf")

# Calculate the extent of the points
points_GPS_extent <- data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  dplyr::filter(S2_data == T | Landsat_data == T ) %>%
  dplyr::filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS") %>%
  summarise(lon_min = min(Lon_updated, na.rm = TRUE),
            lon_max = max(Lon_updated, na.rm = TRUE),
            lat_min = min(Lat_updated, na.rm = TRUE),
            lat_max = max(Lat_updated, na.rm = TRUE))

# Add padding to the extent (adjust as needed)
padding <- 2  # Adjust padding to your preference
x_limits <- c(points_GPS_extent$lon_min - padding,
              points_GPS_extent$lon_max + padding)
y_limits <- c(points_GPS_extent$lat_min - padding,
              points_GPS_extent$lat_max + padding)

# Create the zoomed map
ggplot() +
  geom_sf(data = world, fill = "lightblue", color = "gray") +
  geom_point(data = data_validation %>%
               dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
               dplyr::filter(S2_data == T | Landsat_data == T ) %>%
               dplyr::filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS"),
             aes(x = Lon_updated, y = Lat_updated, color = EUNISa_1),
             size = 1) +
  coord_sf(xlim = x_limits, ylim = y_limits) +
  theme_minimal()

Number of GPS points by Country:

data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  dplyr::filter(S2_data == T | Landsat_data == T ) %>%
  dplyr::filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS") %>%
  count(Country)

Points ReSurvey

# Calculate the extent of the points
points_resurvey_extent <- data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  dplyr::filter(S2_data == T | Landsat_data == T ) %>%
  summarise(lon_min = min(Lon_updated, na.rm = TRUE),
            lon_max = max(Lon_updated, na.rm = TRUE),
            lat_min = min(Lat_updated, na.rm = TRUE),
            lat_max = max(Lat_updated, na.rm = TRUE))

# Add padding to the extent (adjust as needed)
padding <- 2  # Adjust padding to your preference
x_limits <- c(points_resurvey_extent$lon_min - padding,
              points_resurvey_extent$lon_max + padding)
y_limits <- c(points_resurvey_extent$lat_min - padding,
              points_resurvey_extent$lat_max + padding)

# Create the zoomed map
ggplot() +
  geom_sf(data = world, fill = "lightblue", color = "gray") +
  geom_point(data = data_validation %>%
               dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
               dplyr::filter(S2_data == T | Landsat_data == T ),
             aes(x = Lon_updated, y = Lat_updated, color = EUNISa_1),
             size = 1) +
  coord_sf(xlim = x_limits, ylim = y_limits) +
  theme_minimal()

Number of ReSurvey points by Country:

data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  dplyr::filter(S2_data == T | Landsat_data == T ) %>%
  count(Country)

Cordillera data

AlpineGrasslands_indices <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrassland_Sentinel_Plot_Allyear_Allmetrics.csv")
AlpineGrasslands_phen <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
AlpineGrasslands_CH <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_CanopyHeight_1m.csv")
VegetationTypes_indices <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Sentinel_Plot_AllYear_Allmetrics.csv")
VegetationTypes_phen <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
VegetationTypes_CH <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_CanopyHeight_1m.csv")
AlpineGrasslands <- AlpineGrasslands_indices %>%
  select(-`system:index`, -.geo, -Localidad) %>%
  rename(Hábitat = "H�bitat") %>% 
  full_join(AlpineGrasslands_phen  %>%
              select(-`system:index`, -.geo, -Localidad) %>%
              rename(Hábitat = "H�bitat")) %>%
  full_join(AlpineGrasslands_CH  %>%
              select(-`system:index`, -.geo, -Localidad)) %>%
  select(-Date__year, - `Precisi�n`) %>%
  mutate(DATE = ymd(DATE)) %>%
  rename(ID = "Releve_num") %>%
  mutate(ID = as.character(ID)) %>%
  mutate(layer = "AlpineGrasslands")
VegetationTypes <- VegetationTypes_indices %>%
  select(-`system:index`, -.geo) %>%
  full_join(VegetationTypes_phen  %>%
              select(-`system:index`, -.geo)) %>%
  full_join(VegetationTypes_CH  %>%
              select(-`system:index`, -.geo)) %>%
  rename(Hábitat = "TYPE") %>%
  mutate(layer = "VegetationTypes")

Merge both datasets:

cordillera <- bind_rows(
  AlpineGrasslands %>% select(DATE, ID, starts_with("NDMI"),
                              starts_with("NDVI"), Hábitat, "EOS_DOY",
                              "Peak_DOY", "SOS_DOY", "Season_Length",
                              "canopy_height", "layer"),
  VegetationTypes %>% select(DATE, ID, starts_with("NDMI"),
                              starts_with("NDVI"), Hábitat, "EOS_DOY",
                              "Peak_DOY", "SOS_DOY", "Season_Length",
                              "canopy_height", "layer")
  ) %>%
  mutate(EUNISa_1 = case_when(
    Hábitat = str_detect(Hábitat, "Pastizal|Cervunal|grassland|meadow") ~ "R",
    Hábitat = str_detect(Hábitat, "forest") ~ "T",
    Hábitat = str_detect(Hábitat, "Scrub|scrub|Shrubland|shrubland|shrub|Heathland") ~ "S",
    Hábitat = str_detect(Hábitat, "Suelo|Scree|scree|cliff") ~ "U",
    Hábitat = is.na(Hábitat) ~ "R",
    TRUE ~ NA_character_),
    EUNISa_1_descr = case_when(
      EUNISa_1 == "R" ~ "Grasslands",
      EUNISa_1 == "T" ~ "Forests and other wooded land",
      EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISa_1 == "U" ~ "Inland habitats with no or little soil")
    )

NDVI, NDMI

distr_plot(cordillera,
           c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"), 
           c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))
distr_plot(cordillera,
           c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"), 
           c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))

Session info

sessionInfo()
LS0tDQp0aXRsZTogIlNjcmlwdCB0byB2YWxpZGF0ZSBwb2ludHMgaW4gUmVTdXJ2ZXkgZGF0YWJhc2UgdXNpbmcgUlMgZGF0YSAoUzIgb25seSkiDQpzdWJ0aXRsZTogIlZhbGlkYXRpb24gZG9uZSB3aXRoIEFMTCBwb2ludHMgKGFsbCBvYnNlcnZhdGlvbnMpIg0KYXV0aG9yOiAiQWxpY2lhIFZhbGTDqXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UpDQpgYGANCg0KVGhpcyBSIHNjcmlwdCBpcyB1c2VkIHRvIHZhbGlkYXRlIHRoZSBwb2ludHMgaW4gdGhlIFJlU3VydmV5IGRhdGFiYXNlIHVzaW5nIFJTIGluZGljYXRvcnMgKGluZGljZXMgKyBwaGVub2xvZ3kgKyBjYW5vcHkgaGVpZ2h0KS4NCg0KIyBMb2FkIGxpYnJhcmllcw0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShoZXJlKQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShzZikNCmxpYnJhcnkocm5hdHVyYWxlYXJ0aCkNCmxpYnJhcnkoZHRwbHlyKQ0KbGlicmFyeShsbWU0KQ0KbGlicmFyeShsbWVyVGVzdCkNCmxpYnJhcnkoY2FyKQ0KbGlicmFyeShnZ2VmZmVjdHMpDQpsaWJyYXJ5KHBhcnR5KQ0KbGlicmFyeShwYXJ0eWtpdCkNCmxpYnJhcnkobW9yZXBhcnR5KQ0KbGlicmFyeShkb1BhcmFsbGVsKQ0KbGlicmFyeShzdHJ1Y2NoYW5nZSkNCmxpYnJhcnkoZ2dwYXJ0eSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KG1vcmVwYXJ0eSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkocmxhbmcpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KGJlZXByKQ0KbGlicmFyeShmb3JlYWNoKQ0KbGlicmFyeShwZXJtaW1wKQ0KYGBgDQoNCiMgRGVmaW5lIHByaW50YWxsIGZ1bmN0aW9uDQoNCmBgYHtyfQ0KcHJpbnRhbGwgPC0gZnVuY3Rpb24odGliYmxlKSB7DQogIHByaW50KHRpYmJsZSwgd2lkdGggPSBJbmYpDQogIH0NCmBgYA0KDQojIExvYWQgZ2VvbV9mbGF0X3Zpb2xpbiBwbG90DQoNCmBgYHtyfQ0Kc291cmNlKCJodHRwczovL2dpc3QuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Jlbm1hcndpY2svMmExYmIwMTMzZmY1NjhjYmUyOGQvcmF3L2ZiNTNiZDk3MTIxZjdmOWNlOTQ3ODM3ZWYxYTRjNjVhNzNiZmZiM2YvZ2VvbV9mbGF0X3Zpb2xpbi5SIikNCmBgYA0KDQojIExvYWQgcHJldmlvdXNseSBjcmVhdGVkIG9iamVjdHMNCg0KYGBge3J9DQojIERlZmluZSB0aGUgZm9sZGVyIHBhdGgNCmZvbGRlcl9wYXRoIDwtIGhlcmUoIm9iamVjdHMiLCAiMTAiKQ0KDQojIExpc3QgYWxsIC5SRGF0YSBvciAucmRhIGZpbGVzIGluIHRoZSBmb2xkZXINCnJkYXRhX2ZpbGVzIDwtIGxpc3QuZmlsZXMoZm9sZGVyX3BhdGgsIGZ1bGwubmFtZXMgPSBUUlVFKQ0KDQojIExvYWQgZWFjaCBmaWxlDQpsYXBwbHkocmRhdGFfZmlsZXMsIGxvYWQsIGVudmlyID0gLkdsb2JhbEVudikNCmBgYA0KDQojIFJlYWQgZGF0YQ0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbjwtcmVhZF90c3YoaGVyZSgiZGF0YSIsICJjbGVhbiIsImZpbmFsX1JTX2RhdGFfYmFuZHNfUzJfYWxsLmNzdiIpKQ0KYGBgDQoNCk5vIHBhcnNpbmcgaXNzdWVzIQ0KDQojIFNvbWUgZGF0YSBtYW5hZ2VuZW10DQoNCiMjIFRPLURPOiBNaXNzaW5nIGRhdGEgY2hlY2tzDQoNCkRvIHdoZW4gYWxsIFJTIGRhdGEgaXMgcmVhZHkhDQoNCiMgRGlzdHJpYnV0aW9ucyBhbGwgYmlvcmVnaW9ucw0KDQojIyBJbmRpY2VzDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjcmVhdGUgaGlzdG9ncmFtcw0KcGxvdF9oaXN0b2dyYW0gPC0gZnVuY3Rpb24oZGF0YSwgeF92YXIsIHhfbGFiZWwpIHsNCiAgZ2dwbG90KGRhdGEgJT4lDQogICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpLA0KICAgICAgICAgYWVzKHggPSAhIXN5bSh4X3ZhcikpKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIndoaXRlIikgKw0KICAgIGxhYnMoeCA9IHhfbGFiZWwsIHkgPSAiRnJlcXVlbmN5IikgKw0KICAgIHRoZW1lX2J3KCkNCn0NCmBgYA0KDQpgYGB7cn0NCiMgRGVmaW5lIGEgZnVuY3Rpb24gdG8gY3JlYXRlIHBsb3RzIHdpdGggdmlvbGluICsgYm94cGxvdCArIHBvaW50cw0KZGlzdHJfcGxvdCA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhcnMsIHlfbGFiZWxzKSB7DQogIGZvciAoaSBpbiBzZXFfYWxvbmcoeV92YXJzKSkgew0KICAgIHlfdmFyIDwtIHlfdmFyc1tbaV1dDQogICAgeV9sYWJlbCA8LSB5X2xhYmVsc1tbaV1dDQogICAgDQogICAgcCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEgJT4lDQogICAgICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSwNCiAgICAgICAgICAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gISFzeW0oeV92YXIpLCBmaWxsID0gRVVOSVNhXzFfZGVzY3IpKSArDQogICAgICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCkgKw0KICAgICAgZ2VvbV9wb2ludChhZXMoeSA9ICEhc3ltKHlfdmFyKSwgY29sb3IgPSBFVU5JU2FfMV9kZXNjciksDQogICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAxLCBhbHBoYSA9IDAuMjUpICsNCiAgICAgIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMiwgb3V0bGllci5zaGFwZSA9IE5BLCBhbHBoYSA9IDAuNSkgKw0KICAgICAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogICAgICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICAgICAgbGFicyh5ID0geV9sYWJlbCwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICAgICAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICAgICAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICAgICAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KICAgIA0KICAgIHByaW50KHApDQogIH0NCn0NCmBgYA0KDQpSYW5nZXMgb2YgbWluIGFuZCBtYXg6DQoNCmBgYHtyfQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJE5EVklfbWF4LCBuYS5ybSA9IFQpICMgTkRWSV9tYXggPiAxIChzbGlnaHRseSkNCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRORE1JX21heCwgbmEucm0gPSBUKSAjIE5ETUlfbWF4ID4gMSAoc2xpZ2h0bHkpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kTkRXSV9tYXgsIG5hLnJtID0gVCkNCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRTQVZJX21heCwgbmEucm0gPSBUKQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJEVWSV9tYXgsIG5hLnJtID0gVCkgIyBFVklfbWF4ID4gMSAoc2xpZ2h0bHkpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kTkRWSV9taW4sIG5hLnJtID0gVCkNCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRORE1JX21pbiwgbmEucm0gPSBUKQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJE5EV0lfbWluLCBuYS5ybSA9IFQpICMgTkRXSV9taW4gPCAtMSAoc2xpZ2h0bHkpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kU0FWSV9taW4sIG5hLnJtID0gVCkNCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRFVklfbWluLCBuYS5ybSA9IFQpICMgRVZJX21pbiA8IC0xIQ0KYGBgDQoNCmBgYHtyfQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoTkRWSV9tYXggPiAxKSkNCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JSBkcGx5cjo6ZmlsdGVyKE5ETUlfbWF4ID4gMSkpDQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUgZHBseXI6OmZpbHRlcihFVklfbWF4ID4gMSkpDQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUgZHBseXI6OmZpbHRlcihORFdJX21pbiA8IC0xKSkNCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JSBkcGx5cjo6ZmlsdGVyKEVWSV9taW4gPCAtMSkpDQpgYGANCg0KSGlzdG9ncmFtcyB0byBjaGVjayB0aGF0IG1heCBhbmQgbWluIHZhbHVlcyBhcmUgb2s6DQoNCmBgYHtyfQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9tYXgiLCAiTkRWSSBtYXgiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRNSV9tYXgiLCAiTkRNSSBtYXgiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRXSV9tYXgiLCAiTkRXSSBtYXgiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9tYXgiLCAiU0FWSSBtYXgiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiRVZJX21heCIsICJFVkkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfbWluIiwgIk5EVkkgbWluIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5ETUlfbWluIiwgIk5ETUkgbWluIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EV0lfbWluIiwgIk5EV0kgbWluIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfbWluIiwgIlNBVkkgbWluIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9taW4iLCAiRVZJIG1pbiIpDQpgYGANCg0KYGBge3J9DQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPiAxIHwgRVZJX21heCA8IC0xKSkNCmRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSU+JQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPiAxIHwgRVZJX21heCA8IC0xKSAlPiUNCiAgY291bnQoYmlvZ2VvLCB1bml0KQ0KYGBgDQoNCk1vc3QgRVZJIHZhbHVlcyBhcmUgb2shDQoNCkRpc3RyaWJ1dGlvbiBwbG90czoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRpc3RyX3Bsb3QoZGF0YV92YWxpZGF0aW9uICU+JSBkcGx5cjo6ZmlsdGVyKEVWSV9taW4gPiAtMC41KSwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiRVZJX21heCIsICJTQVZJX21heCIsICJORE1JX21heCIsICJORFdJX21heCIsDQogICAgICAgICAgICAgIk5EVklfbWluIiwgIkVWSV9taW4iLCAiU0FWSV9taW4iLCAiTkRNSV9taW4iLCAiTkRXSV9taW4iKSwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiRVZJX21heCIsICJTQVZJX21heCIsICJORE1JX21heCIsICJORFdJX21heCIsDQogICAgICAgICAgICAgIk5EVklfbWluIiwgIkVWSV9taW4iLCAiU0FWSV9taW4iLCAiTkRNSV9taW4iLCAiTkRXSV9taW4iKSkNCmBgYA0KDQojIyBDSA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoZGF0YV92YWxpZGF0aW9uLCAiY2Fub3B5X2hlaWdodCIsICJDYW5vcHkgaGVpZ2h0IChtKSIpDQpgYGANCiANCiMjIyBTaG93IGhhYml0YXRzIHdpdGggQ0ggY2F0ZWdvcmllcw0KDQpgYGB7cn0NCmdncGxvdChkYXRhX3ZhbGlkYXRpb24gJT4lDQogICAgICAgICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgICBtdXRhdGUoQ0hfY2F0ID0NCiAgICAgICAgICAgICAgICAgIGZhY3RvcigNCiAgICAgICAgICAgICAgICAgICAgY2FzZV93aGVuKGNhbm9weV9oZWlnaHQgPT0gMCB+ICIwIG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDAgJiBjYW5vcHlfaGVpZ2h0IDw9IDEgfiAiMC0xIG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDEgJiBjYW5vcHlfaGVpZ2h0IDw9MiB+ICIxLTIgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gMiAmIGNhbm9weV9oZWlnaHQgPD01IH4gIjItNSBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbm9weV9oZWlnaHQgPiA1ICYgY2Fub3B5X2hlaWdodCA8PTggfiAiNS04IG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDggfiAiPiA4IG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoY2Fub3B5X2hlaWdodCkgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygNCiAgICAgICAgICAgICAgICAgICAgICAiMCBtIiwgIjAtMSBtIiwgIjEtMiBtIiwgIjItNSBtIiwgIjUtOCBtIiwgIj4gOCBtIikpKSwNCiAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCBmaWxsID0gQ0hfY2F0KSkgKw0KICBnZW9tX2JhcigpICsgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBsYWJlbF9udW1iZXIoKSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSkgKw0KICBsYWJzKHggPSAiRVVOSVMgbGV2ZWwgMSIsIGZpbGwgPSAiQ2Fub3B5IGhlaWdodCIpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOCwgMC43NSksDQogICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiKQ0KYGBgDQoNCiMjIyBTdGF0cyBwZXIgaGFiaXRhdCB0eXBlDQoNCmBgYHtyfQ0KZGF0YV92YWxpZGF0aW9uICU+JQ0KICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzFfZGVzY3IpICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGNhbm9weV9oZWlnaHQsIGxpc3QoDQogICAgbWVhbiA9IG1lYW4sDQogICAgbWVkaWFuID0gbWVkaWFuLA0KICAgIHNkID0gc2QsDQogICAgbWluID0gbWluLA0KICAgIG1heCA9IG1heA0KICAgICksIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KIyMgUGhlbm9sb2d5DQoNCk9ubHkgdXNpbmcgTkRWSS0gYW5kIFNBVkktYmFzZWQgdmFsdWVzIHNvIGZhci4NCg0KTWF4aW11bSBORFZJIHNob3VsZCBiZSBlcXVhbCB0byB2YWx1ZSBhdCBwZWFrPw0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JSBkcGx5cjo6ZmlsdGVyKE5EVklfcG9zX3ZhbHVlICAhPSBORFZJX21heCkpDQpgYGANCg0KTm90IHN1cmUgd2h5IHRoaXMgaGFwcGVucywgYnV0IGluIG1vc3QgY2FzZXMgdGhlIGRpZmZlcmVuY2UgaXMgc21hbGwgKDwgLTAuMSkuIEFueXdheSwgd2Ugc2hvdWxkIHVzZSBvbmx5IG9uZSBvZiB0aGVzZSB0d28gaW4gdGhlIFJGIG1vZGVscy4NCg0KYGBge3J9DQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX3Nvc19kb3kiLCAiTkRWSV9zb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfcG9zX2RveSIsICJORFZJX3Bvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9lb3NfZG95IiwgIk5EVklfZW9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfc29zX2RveSIsICJFVklfc29zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfcG9zX2RveSIsICJFVklfcG9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfZW9zX2RveSIsICJFVklfZW9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX3Nvc19kb3kiLCAiU0FWSV9zb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfcG9zX2RveSIsICJTQVZJX3Bvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9lb3NfZG95IiwgIlNBVklfZW9zX2RveSIpDQpgYGANCg0KYGBge3J9DQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX3Nvc192YWx1ZSIsICJORFZJX3Nvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX3Bvc192YWx1ZSIsICJORFZJX3Bvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2Vvc192YWx1ZSIsICJORFZJX2Vvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfc29zX3ZhbHVlIiwgIkVWSV9zb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiRVZJX3Bvc192YWx1ZSIsICJFVklfcG9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9lb3NfdmFsdWUiLCAiRVZJX2Vvc192YWx1ZSIpDQpgYGANCg0KYGBge3J9DQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2dzZCIsICJORFZJX2dzZCIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfZ3NkIiwgIkVWSV9nc2QiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9nc2QiLCAiU0FWSV9nc2QiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIk5EVklfZGlmZl9wb3Nfc29zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIkVWSV9kaWZmX3Bvc19zb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIlNBVklfZGlmZl9wb3Nfc29zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX2Vvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAgICJFVklfZGlmZl9wb3NfZW9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX2Vvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICAgIk5EVklfZGlmZl9wb3Nfc29zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICAgICAgICAgICAgICAiRVZJX2RpZmZfcG9zX3Nvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9kaWZmX3Bvc19zb3NfZG95IiwNCiAgICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX3Nvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9kaWZmX2Vvc19wb3NfZG95IiwNCiAgICAgICAgICAgICAgICJORFZJX2RpZmZfZW9zX3Bvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiRVZJX2RpZmZfZW9zX3Bvc19kb3kiLCANCiAgICAgICAgICAgICAgICJFVklfZGlmZl9lb3NfcG9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2RpZmZfZW9zX3Bvc19kb3kiLCANCiAgICAgICAgICAgICAgICJTQVZJX2RpZmZfZW9zX3Bvc19kb3kiKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9hdWMiLCAiTkRWSV9hdWMiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiRVZJX2F1YyIsICJFVklfYXVjIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfYXVjIiwgIlNBVklfYXVjIikNCmBgYA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoZGF0YV92YWxpZGF0aW9uLA0KICAgICAgICAgICBjKCJORFZJX3Nvc192YWx1ZSIsIk5EVklfcG9zX3ZhbHVlIiwgIk5EVklfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiRVZJX3Nvc192YWx1ZSIsIkVWSV9wb3NfdmFsdWUiLCAiRVZJX2Vvc192YWx1ZSIsDQogICAgICAgICAgICAgIlNBVklfc29zX3ZhbHVlIiwgIlNBVklfcG9zX3ZhbHVlIiwgIlNBVklfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiTkRWSV9zb3NfZG95IiwiTkRWSV9wb3NfZG95IiwgIk5EVklfZW9zX2RveSIsDQogICAgICAgICAgICAgIkVWSV9zb3NfZG95IiwiRVZJX3Bvc19kb3kiLCAiRVZJX2Vvc19kb3kiLA0KICAgICAgICAgICAgICJTQVZJX3Nvc19kb3kiLCAiU0FWSV9wb3NfZG95IiwgIlNBVklfZW9zX2RveSIpLA0KICAgICAgICAgICBjKCJORFZJX3Nvc192YWx1ZSIsIk5EVklfcG9zX3ZhbHVlIiwgIk5EVklfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiRVZJX3Nvc192YWx1ZSIsIkVWSV9wb3NfdmFsdWUiLCAiRVZJX2Vvc192YWx1ZSIsDQogICAgICAgICAgICAgIlNBVklfc29zX1ZhbHVlIiwgIlNBVklfcG9zX3ZhbHVlIiwgIlNBVklfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiTkRWSV9zb3NfZG95IiwiTkRWSV9wb3NfZG95IiwgIk5EVklfZW9zX2RveSIsDQogICAgICAgICAgICAgIkVWSV9zb3NfZG95IiwiRVZJX3Bvc19kb3kiLCAiRVZJX2Vvc19kb3kiLA0KICAgICAgICAgICAgICJTQVZJX3Nvc19kb3kiLCAiU0FWSV9wb3NfZG95IiwgIlNBVklfZW9zX2RveSIpDQogICAgICAgICAgICkNCmBgYA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoZGF0YV92YWxpZGF0aW9uLA0KICAgICAgICAgICBjKCJORFZJX2dzZCIsIkVWSV9nc2QiLCAiU0FWSV9nc2QiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsICJFVklfZGlmZl9wb3Nfc29zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsICJFVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX3Nvc19kb3kiLCAiRVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfZW9zX3Bvc19kb3kiLCAiRVZJX2RpZmZfZW9zX3Bvc19kb3kiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfZW9zX3Bvc19kb3kiKSwNCiAgICAgICAgICAgYygiTkRWSV9nc2QiLCJFVklfZ3NkIiwgIlNBVklfZ3NkIiwNCiAgICAgICAgICAgICAiTkRWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLCAiRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgIlNBVklfZGlmZl9wb3Nfc29zX1ZhbHVlIiwNCiAgICAgICAgICAgICAiTkRWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLCAiRVZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsDQogICAgICAgICAgICAgIlNBVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiTkRWSV9kaWZmX3Bvc19zb3NfZG95IiwgIkVWSV9kaWZmX3Bvc19zb3NfZG95IiwNCiAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19zb3NfZG95IiwNCiAgICAgICAgICAgICAiTkRWSV9kaWZmX2Vvc19wb3NfZG95IiwgIkVWSV9kaWZmX2Vvc19wb3NfZG95IiwNCiAgICAgICAgICAgICAiU0FWSV9kaWZmX2Vvc19wb3NfZG95IikNCiAgICAgICAgICAgKQ0KYGBgDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24sDQogICAgICAgICAgIGMoIk5EVklfYXVjIiwgIkVWSV9hdWMiLCAiU0FWSV9hdWMiKSwNCiAgICAgICAgICAgICBjKCJORFZJX2F1YyIsICJFVklfYXVjIiwgIlNBVklfYXVjIikpDQpgYGANCg0KIyBUQkQ6IERpc3RyaWJ1dGlvbnMgcGVyIGJpb3JlZ2lvbg0KDQpgYGB7cn0NCiMgRGVmaW5lIGEgZnVuY3Rpb24gdG8gY3JlYXRlIHBsb3RzIHdpdGggdmlvbGluICsgYm94cGxvdCArIHBvaW50cw0KZGlzdHJfcGxvdF9iaW9nZW8gPC0gZnVuY3Rpb24oZGF0YSwgeV92YXJzLCB5X2xhYmVscykgew0KICBwbG90cyA8LSBsaXN0KCkNCiAgDQogIGZvciAoaSBpbiBzZXFfYWxvbmcoeV92YXJzKSkgew0KICAgIHlfdmFyIDwtIHlfdmFyc1tbaV1dDQogICAgeV9sYWJlbCA8LSB5X2xhYmVsc1tbaV1dDQogICAgDQogICAgcCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEgJT4lDQogICAgICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSwNCiAgICAgICAgICAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gISFzeW0oeV92YXIpLCBmaWxsID0gRVVOSVNhXzFfZGVzY3IpKSArDQogICAgICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCkgKw0KICAgICAgZ2VvbV9wb2ludChhZXMoeSA9ICEhc3ltKHlfdmFyKSwgY29sb3IgPSBFVU5JU2FfMV9kZXNjciksDQogICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAxLCBhbHBoYSA9IDAuMjUpICsNCiAgICAgIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMiwgb3V0bGllci5zaGFwZSA9IE5BLCBhbHBoYSA9IDAuNSkgKw0KICAgICAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogICAgICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICAgICAgbGFicyh5ID0geV9sYWJlbCwgeCA9ICJFVU5JU2FfMV9kZXNjciIpICsNCiAgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgICAgIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsNCiAgICAgIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkgKyBmYWNldF93cmFwKH4gYmlvZ2VvKQ0KICAgIA0KICAgIHBsb3RzW1t5X3Zhcl1dIDwtIHANCiAgfQ0KICANCiAgcmV0dXJuKHBsb3RzKQ0KfQ0KYGBgDQoNCiMjIEluZGljZXMNCg0KRGlzdHJpYnV0aW9uIHBsb3RzOg0KDQojIyBDSA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3RfYmlvZ2VvKGRhdGFfdmFsaWRhdGlvbiwgImNhbm9weV9oZWlnaHQiLCAiQ2Fub3B5IGhlaWdodCAobSkiKQ0KYGBgDQoNCiMjIFBoZW5vbG9neQ0KDQojIERlZmluZSBmdW5jdGlvbnMgZm9yIFJGIG1vZGVscw0KDQojIyBGdW5jdGlvbiBmb3IgZml0dGluZyBSRiBtb2RlbHMNCg0KUkYgbW9kZWxzIGZpdHRlZCB1c2luZyB0aGUgY29uZGl0aW9uYWwgaW5mZXJlbmNlIHZlcnNpb24gb2YgcmFuZG9tIGZvcmVzdCAoZmlyc3QgY2ZvcmVzdCBpbiBwYWNrYWdlIHBhcnR5LCBub3cgZmFzdGNmb3Jlc3QgaW4gcGFja2FnZSBtb3JlcGFydHkpLiBTdWdnZXN0ZWQgaWYgdGhlIGRhdGEgYXJlIGhpZ2hseSBjb3JyZWxhdGVkLiBDZm9yZXN0IGlzIG1vcmUgc3RhYmxlIGluIGRlcml2aW5nIHZhcmlhYmxlIGltcG9ydGFuY2UgdmFsdWVzIGluIHRoZSBwcmVzZW5jZSBvZiBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMsIHRodXMgcHJvdmlkaW5nIGJldHRlciBhY2N1cmFjeSBpbiBjYWxjdWxhdGluZyB2YXJpYWJsZSBpbXBvcnRhbmNlIChyZWYgYmVsb3cpLg0KDQpIb3Rob3JuLCBULiwgSG9ybmlrLCBLLiBhbmQgWmVpbGVpcywgQS4gKDIwMDYpIFVuYmlhc2VkIFJlY3Vyc2l2ZSBQb3J0aW9uaW5nOiBBIENvbmRpdGlvbmFsIEluZmVyZW5jZSBGcmFtZXdvcmsuIEpvdXJuYWwgb2YgQ29tcHV0YXRpb25hbCBhbmQgR3JhcGhpY2FsIFN0YXRpc3RpY3MsIDE1LCA2NTEtDQo2NzQuIGh0dHA6Ly9keC5kb2kub3JnLzEwLjExOTgvMTA2MTg2MDA2WDEzMzkzMw0KDQpDaG9vc2UgdGhlIGh5cGVycGFyYW1ldGVyIG10cnkgYmFzZWQgb24gdGhlIHNxdWFyZSByb290IG9mIHRoZSBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlczoNCg0KSGFzdGllLCBULiwgVGlic2hpcmFuaSwgUi4sICYgRnJpZWRtYW4sIEouICgyMDA5KS4gVGhlIGVsZW1lbnRzIG9mIHN0YXRpc3RpY2FsDQpsZWFybmluZzogRGF0YSBtaW5pbmcsIGluZmVyZW5jZSwgYW5kIHByZWRpY3Rpb24uIFNwcmluZ2VyIFNjaWVuY2UgJg0KQnVzaW5lc3MgTWVkaWEuDQoNCk1heWJlIFRPX0RPOg0KV2UgdmFyaWF0ZWQgbnRyZWUgZnJvbSA1MCB0byA4MDAgaW4gc3RlcHMgb2YgNTAsIGxlYXZpbmcgbXRyeSBjb25zdGFudCBhdCAyLiBUaXMgcGFyYW1ldGVyIHZhcmlhdGlvbiBzaG93ZWQgdGhhdCBudHJlZT01MDAgd2FzIG9wdGltYWwsIHdoaWxlIGhpZ2hlciBudHJlZSBsZWQgdG8gbm8gZnVydGhlciBtb2RlbCBpbXByb3ZlbWVudCAoU3VwcGxlbWVudGFyeSBGaWcuIFMxMCkuIFN1YnNlcXVlbnRseSwgdGhlIGh5cGVycGFyYW1ldGVyIG10cnkgd2FzIHZhcmllZCBmcm9tIDIgdG8gOCB3aXRoIGNvbnN0YW50IG50cmVlPTUwMC4gSGVyZSwgbXRyeT0zIGxlZCB0byB0aGUgYmVzdCByZXN1bHRzIGluIGFsbW9zdCBhbGwgY2FzZXMgKFN1cHBsZW1lbnRhcnkgRmlnLiBTMTEpLiBDb25zZXF1ZW50bHksIHdlIGNob3NlIG50cmVlPTUwMCBhbmQgbXRyeT0zIGZvciBvdXIgbWFpbiBhbmFseXNpcyBhY3Jvc3MgYWxsIHN0dWR5IHNpdGVzLg0KDQpEZWZpbmUgYSBmdW5jdGlvbiB0byBydW4gZmFzdGNmb3Jlc3QgbW9kZWxzOg0KDQpgYGB7cn0NCnJ1bl9yZiA8LSBmdW5jdGlvbih2YXJzX1JGLCB0cmFpbl9kYXRhLCByZXNwb25zZV92YXIsIG50cmVlID0gNTAwKSANCiAgew0KICANCiAgIyBEZXRlY3QgYW5kIHJlZ2lzdGVyIGF2YWlsYWJsZSBjb3JlcyAobGVhdmUgb25lIGZyZWUpDQogIG5fY29yZXMgPC0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSAxDQogIGNsIDwtIG1ha2VDbHVzdGVyKG5fY29yZXMpDQogIHJlZ2lzdGVyRG9QYXJhbGxlbChjbCkNCiAgDQogIHRyYWluX25hbWUgPC0gZGVwYXJzZShzdWJzdGl0dXRlKHRyYWluX2RhdGEpKQ0KICANCiAgIyBFeHBvcnQgbmVjZXNzYXJ5IHZhcmlhYmxlcyB0byB0aGUgY2x1c3Rlcg0KICBjbHVzdGVyRXhwb3J0KGNsLCB2YXJsaXN0ID0gYygidmFyc19SRiIsIHRyYWluX25hbWUpKQ0KICANCiAgIyBTZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQogIHNldC5zZWVkKDEyMykNCiAgDQogICMgTWVhc3VyZSBleGVjdXRpb24gdGltZQ0KICBleGVjdXRpb25fdGltZSA8LSBzeXN0ZW0udGltZSh7DQogICAgcmZfbW9kZWwgPC0gZmFzdGNmb3Jlc3QoDQogICAgICBmb3JtdWxhID0gcmVmb3JtdWxhdGUodmFyc19SRiwgcmVzcG9uc2UgPSByZXNwb25zZV92YXIpLA0KICAgICAgZGF0YSA9IHRyYWluX2RhdGEsDQogICAgICBjb250cm9scyA9IHBhcnR5OjpjZm9yZXN0X2NvbnRyb2woDQogICAgICAgIG10cnkgPSByb3VuZChzcXJ0KGxlbmd0aCh2YXJzX1JGKSkpLA0KICAgICAgICBudHJlZSA9IG50cmVlDQogICAgICApLA0KICAgICAgcGFyYWxsZWwgPSBUUlVFDQogICAgKQ0KICB9KQ0KICANCiAgIyBTdG9wIHRoZSBjbHVzdGVyDQogIHN0b3BDbHVzdGVyKGNsKQ0KICANCiAgIyBSZXR1cm4gYm90aCB0aGUgbW9kZWwgYW5kIGV4ZWN1dGlvbiB0aW1lDQogIGxpc3QobW9kZWwgPSByZl9tb2RlbCwgdGltZSA9IGV4ZWN1dGlvbl90aW1lKQ0KfQ0KYGBgDQoNCiMjIEZ1bmN0aW9uIHRvIGNvbXB1dGUgdmFyaWFibGUgaW1wb3J0YW5jZQ0KDQpgYGB7cn0NCiMgY29tcHV0ZV92YXJpbXAgPC0gZnVuY3Rpb24obW9kZWwsIG5wZXJtID0gMTAwLCANCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2NvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSAxKSB7DQojICAgIyBTZXQgdXAgcGFyYWxsZWwgYmFja2VuZA0KIyAgIGNsIDwtIG1ha2VDbHVzdGVyKG5fY29yZXMpDQojICAgcmVnaXN0ZXJEb1BhcmFsbGVsKGNsKQ0KIyAgIA0KIyAgICMgTWVhc3VyZSBleGVjdXRpb24gdGltZQ0KIyAgIGV4ZWN1dGlvbl90aW1lIDwtIHN5c3RlbS50aW1lKHsNCiMgICAgIHZhcmltcF9saXN0IDwtIGZvcmVhY2goaSA9IDE6bnBlcm0sIC5jb21iaW5lID0gJysnLCANCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnBhY2thZ2VzID0gInBhcnR5IikgJWRvcGFyJSB7DQojICAgICAgIHZhcmltcChtb2RlbCwgY29uZGl0aW9uYWwgPSBGQUxTRSwgbnBlcm0gPSAxKQ0KIyAgICAgfQ0KIyAgIH0pDQojICAgDQojICAgc3RvcENsdXN0ZXIoY2wpDQojICAgDQojICAgIyBBdmVyYWdlIHRoZSByZXN1bHRzDQojICAgdmFyaW1wX2F2ZyA8LSB2YXJpbXBfbGlzdCAvIG5wZXJtDQojICAgDQojICAgcmV0dXJuKGxpc3QodmFyaW1wID0gdmFyaW1wX2F2ZywgdGltZSA9IGV4ZWN1dGlvbl90aW1lKSkNCiMgfQ0KYGBgDQoNClVzaW5nIHBlcm1pbXAoKSBlbiBwZXJtaW1wIHBhY2thZ2U6DQpodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcGVybWltcC92aWduZXR0ZXMvcGVybWltcC1wYWNrYWdlLmh0bWwjZm4xDQoNCmBgYHtyfQ0KY29tcHV0ZV92YXJpbXAgPC0gZnVuY3Rpb24obW9kZWwsIG5wZXJtID0gMTAwKSB7DQoNCiAgIyBNZWFzdXJlIGV4ZWN1dGlvbiB0aW1lDQogIGV4ZWN1dGlvbl90aW1lIDwtIHN5c3RlbS50aW1lKHsNCiAgICB2YXJpbXBfcmVzdWx0IDwtIHBlcm1pbXAobW9kZWwsIGNvbmRpdGlvbmFsID0gRkFMU0UsIHByb2dyZXNzQmFyID0gVFJVRSkNCiAgfSkNCg0KICByZXR1cm4obGlzdCh2YXJpbXAgPSB2YXJpbXBfcmVzdWx0LCB0aW1lID0gZXhlY3V0aW9uX3RpbWUpKQ0KfQ0KYGBgDQoNCiMjIEZ1bmN0aW9uIHRvIGNvbXB1dGUgQ09ORElUSU9OQUwgdmFyaWFibGUgaW1wb3J0YW5jZQ0KDQpgYGB7cn0NCmNvbXB1dGVfdmFyaW1wX2NvbmQgPC0gZnVuY3Rpb24obW9kZWwsIG5wZXJtID0gMTAwKSB7DQoNCiAgIyBNZWFzdXJlIGV4ZWN1dGlvbiB0aW1lDQogIGV4ZWN1dGlvbl90aW1lIDwtIHN5c3RlbS50aW1lKHsNCiAgICB2YXJpbXBfcmVzdWx0IDwtIHBlcm1pbXAobW9kZWwsIGNvbmRpdGlvbmFsID0gVFJVRSwgcHJvZ3Jlc3NCYXIgPSBUUlVFKQ0KICB9KQ0KDQogIHJldHVybihsaXN0KHZhcmltcCA9IHZhcmltcF9yZXN1bHQsIHRpbWUgPSBleGVjdXRpb25fdGltZSkpDQp9DQpgYGANCg0KIyMgRnVuY3Rpb24gdG8gY29tcHV0ZSBST0MgKGxldmVsIDEpDQoNCmBgYHtyfQ0KY29tcHV0ZV9yb2NfbGV2ZWwxIDwtIGZ1bmN0aW9uKG1vZGVsLCB0ZXN0X2RhdGEpIHsNCiAgIyBNZWFzdXJlIGV4ZWN1dGlvbiB0aW1lDQogIGV4ZWN1dGlvbl90aW1lIDwtIHN5c3RlbS50aW1lKHsNCiAgICAjIFN0ZXAgMTogUHJlZGljdCBwcm9iYWJpbGl0aWVzDQogICAgcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KG1vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhLCB0eXBlID0gInByb2IiKQ0KICAgIA0KICAgICMgU3RlcCAyOiBDb252ZXJ0IGxpc3Qgb2YgbWF0cmljZXMgdG8gYSBwcm9wZXIgZGF0YSBmcmFtZQ0KICAgIHByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQogICAgcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHByb2JfbWF0cml4KQ0KICAgIGNvbG5hbWVzKHByb2JfZGYpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKQ0KICAgIA0KICAgICMgU3RlcCAzOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCiAgICBhY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KICAgIA0KICAgICMgU3RlcCA0OiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQogICAgYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KICAgIGNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCiAgICANCiAgICAjIFN0ZXAgNTogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcw0KICAgIHJvY19kYXRhIDwtIGxhcHBseShsZXZlbHMoYWN0dWFsKSwgZnVuY3Rpb24oY2xhc3MpIHsNCiAgICAgIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogICAgICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgICAgIGRhdGEuZnJhbWUoDQogICAgICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgICAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICAgICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgICAgICkNCiAgICB9KSAlPiUgYmluZF9yb3dzKCkNCiAgfSkNCiAgDQogICMgUmV0dXJuIGJvdGggUk9DIGRhdGEgYW5kIGV4ZWN1dGlvbiB0aW1lDQogIHJldHVybihsaXN0KHJvYyA9IHJvY19kYXRhLCB0aW1lID0gZXhlY3V0aW9uX3RpbWUpKQ0KfQ0KYGBgDQoNCiMjIEZ1bmN0aW9uIHRvIGNvbXB1dGUgUk9DIChsZXZlbCAyKQ0KDQpgYGB7cn0NCmNvbXB1dGVfcm9jX2xldmVsMiA8LSBmdW5jdGlvbihtb2RlbCwgdGVzdF9kYXRhKSB7DQogICMgTWVhc3VyZSBleGVjdXRpb24gdGltZQ0KICBleGVjdXRpb25fdGltZSA8LSBzeXN0ZW0udGltZSh7DQogICAgIyBTdGVwIDE6IFByZWRpY3QgcHJvYmFiaWxpdGllcw0KICAgIHByb2JhYmlsaXRpZXMgPC0gcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSwgdHlwZSA9ICJwcm9iIikNCiAgICANCiAgICAjIFN0ZXAgMjogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCiAgICBwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KICAgIHByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCiAgICBjb2xuYW1lcyhwcm9iX2RmKSA8LSBjKCJRMSIsICJRMiIsICJRNCIsICJRNSIsICJSMSIsICJSMiIsICJSMyIsICJSNCIsICJSNSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAiUjYiLCAiUzMiLCAiUzQiLCAiVDEiLCAiVDMiKQ0KICAgIA0KICAgICMgU3RlcCAzOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCiAgICBhY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YSRFVU5JU2FfMiwgDQogICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJRMSIsICJRMiIsICJRNCIsICJRNSIsICJSMSIsICJSMiIsICJSMyIsICJSNCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSNSIsICJSNiIsICJTMyIsICJTNCIsICJUMSIsICJUMyIpKQ0KICAgIA0KICAgICMgU3RlcCA0OiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQogICAgYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KICAgIGNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCiAgICANCiAgICAjIFN0ZXAgNTogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcw0KICAgIHJvY19kYXRhIDwtIGxhcHBseShsZXZlbHMoYWN0dWFsKSwgZnVuY3Rpb24oY2xhc3MpIHsNCiAgICAgIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogICAgICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgICAgIGRhdGEuZnJhbWUoDQogICAgICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgICAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICAgICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgICAgICkNCiAgICB9KSAlPiUgYmluZF9yb3dzKCkNCiAgfSkNCiAgDQogICMgUmV0dXJuIGJvdGggUk9DIGRhdGEgYW5kIGV4ZWN1dGlvbiB0aW1lDQogIHJldHVybihsaXN0KHJvYyA9IHJvY19kYXRhLCB0aW1lID0gZXhlY3V0aW9uX3RpbWUpKQ0KfQ0KYGBgDQoNCiMgRGVmaW5lIGxpc3Qgb2YgcHJlZGljdG9yIHZhcnMNCg0KYGBge3J9DQp2YXJzX1JGIDwtIGMoDQogICMgTWluIHZhbHVlcyBvZiBhbGwgaW5kaWNlcw0KICAiTkRWSV9taW4iLCAiRVZJX21pbiIsICJORE1JX21pbiIsICJORFdJX21pbiIsICJTQVZJX21pbiIsDQogICMgTWF4IHZhbHVlcyBvZiBORE1JIGFuZCBORFdJDQogICJORE1JX21heCIsICJORFdJX21heCIsDQogICMgQVVDIG9mIE5EVkksIEVWSSBhbmQgU0FWSQ0KICAiTkRWSV9hdWMiLCAiRVZJX2F1YyIsICJTQVZJX2F1YyIsDQogICMgVmFsdWVzIG9mIE5EVkksIEVWSSBhbmQgU0FWSSBhdCBzb3MsIHBvcyBhbmQgZW9zDQogICJORFZJX3Nvc192YWx1ZSIsICJORFZJX3Bvc192YWx1ZSIsICJORFZJX2Vvc192YWx1ZSIsDQogICJFVklfc29zX3ZhbHVlIiwgIkVWSV9wb3NfdmFsdWUiLCAiRVZJX2Vvc192YWx1ZSIsIA0KICAiU0FWSV9zb3NfdmFsdWUiLCAiU0FWSV9wb3NfdmFsdWUiLCAiU0FWSV9lb3NfdmFsdWUiLA0KICAjIERpZmZlcmVuY2VzIHBvcy1zb3MgaW4gdmFsdWUgYW5kIGRveQ0KICAiTkRWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLCAiRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsICJTQVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICJORFZJX2RpZmZfcG9zX3Nvc19kb3kiLCAiRVZJX2RpZmZfcG9zX3Nvc19kb3kiLCAiU0FWSV9kaWZmX3Bvc19zb3NfZG95IiwNCiAgIyBEaWZmZXJlbmNlcyBwb3MtZW9zIGluIHZhbHVlIGFuZCBkb3kNCiAgIk5EVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwgIkVWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLCJTQVZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsDQogICJORFZJX2RpZmZfZW9zX3Bvc19kb3kiLCAiRVZJX2RpZmZfZW9zX3Bvc19kb3kiLCAiU0FWSV9kaWZmX2Vvc19wb3NfZG95IiwNCiAgIyBHcm93aW5nIHNlYXNvbiBkdXJhdGlvbg0KICAiTkRWSV9nc2QiLCAiRVZJX2dzZCIsICJTQVZJX2dzZCIsDQogICMgQ2Fub3B5IGhlaWdodA0KICAiY2Fub3B5X2hlaWdodCIpDQpgYGANCg0KIyBDb3JyZWxhdGlvbg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEwIDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogICMgUmVtb3ZlIGFsbCByb3dzIHdpdGggd3JvbmcgdmFsdWVzIG9mIGluZGljZXMgKG5vdCBiZXR3ZWVuIC0xIGFuZCAxKQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EVklfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5ETUlfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EV0lfbWluID49IC0xKSAlPiUNCiAgIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluIHByZWRpY3RvcnMNCiAgZHBseXI6OmZpbHRlcihpZl9hbGwoYWxsX29mKHZhcnNfUkYpLCB+ICFpcy5uYSguKSkpICU+JQ0KICAjIEtlZXAgb25seSByb3dzIHdpdGggZGlmZmVyZW5jZXMgPiAwDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGNvbnRhaW5zKCJkaWZmIiksIH4gLnggPiAwKSkgJT4lDQogICMgU2VsZWN0IG9ubHkgdmFyaWFibGVzIG5lZWRlZA0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIEVVTklTYV8xLCBhbGxfb2YodmFyc19SRikpDQpgYGANCg0KQ29ycmVsYXRpb24gb2YgYWxsIHZhcmlhYmxlcyB0byBiZSBpbmNsdWRlZCBpbiBSRiBtb2RlbHM6DQoNCmBgYHtyfQ0KY29ycnBsb3QoZmlsdGVyZWRfZGF0YTAgJT4lIA0KICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGFsbF9vZih2YXJzX1JGKSkgJT4lDQogICAgICAgICAgIGNvcih1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIiksDQogICAgICAgICBtZXRob2QgPSAiY29sb3IiLCB0eXBlID0gInVwcGVyIiwgdGwuY29sID0gImJsYWNrIiwgdGwuc3J0ID0gNDUpDQpgYGANCg0KYGBge3J9DQojIENvbXB1dGUgY29ycmVsYXRpb24gbWF0cml4DQpjb3JfbWF0cml4IDwtIGZpbHRlcmVkX2RhdGEwICU+JQ0KICBkcGx5cjo6c2VsZWN0KGFsbF9vZih2YXJzX1JGKSkgJT4lDQogIGNvcih1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikNCg0KIyBHZXQgdGhlIHVwcGVyIHRyaWFuZ2xlIG9mIHRoZSBtYXRyaXggd2l0aG91dCB0aGUgZGlhZ29uYWwNCmNvcl9tYXRyaXhbbG93ZXIudHJpKGNvcl9tYXRyaXgsIGRpYWcgPSBUUlVFKV0gPC0gTkENCg0KIyBGaW5kIHZhcmlhYmxlIHBhaXJzIHdpdGggY29ycmVsYXRpb24gPiAwLjcgb3IgPCAtMC43DQpoaWdoX2NvcnJfcGFpcnMgPC0gd2hpY2goYWJzKGNvcl9tYXRyaXgpID4gMC43LCBhcnIuaW5kID0gVFJVRSkNCg0KIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIHRoZSByZXN1bHRzDQpjb3JfcGFpcnNfZGYgPC0gZGF0YS5mcmFtZSgNCiAgVmFyMSA9IHJvd25hbWVzKGNvcl9tYXRyaXgpW2hpZ2hfY29ycl9wYWlyc1ssIDFdXSwNCiAgVmFyMiA9IGNvbG5hbWVzKGNvcl9tYXRyaXgpW2hpZ2hfY29ycl9wYWlyc1ssIDJdXSwNCiAgQ29ycmVsYXRpb24gPSBjb3JfbWF0cml4W2hpZ2hfY29ycl9wYWlyc10NCikNCg0KIyBWaWV3IHRoZSByZXN1bHQNCnByaW50KGNvcl9wYWlyc19kZikNCmBgYA0KDQoNCiMgRGVmaW5lIGxpc3Qgb2YgdW5jb3JyZWxhdGVkIHByZWRpY3RvciB2YXJzDQoNCmBgYHtyfQ0KdmFyc19SRl91bmNvcnIgPC0gYygNCiAgIyBNaW4gdmFsdWVzDQogICJORE1JX21pbiIsICJORFdJX21pbiIsICJTQVZJX21pbiIsDQogICMgTWF4IHZhbHVlIG9mIE5ETUkNCiAgIk5ETUlfbWF4IiwNCiAgIyBBVUMNCiAgIlNBVklfYXVjIiwNCiAgIyBWYWx1ZXMgb2YgU0FWSSBhdCBzb3MsIHBvcyBhbmQgZW9zDQogICJTQVZJX3Nvc192YWx1ZSIsICJTQVZJX3Bvc192YWx1ZSIsICJTQVZJX2Vvc192YWx1ZSIsDQogICMgRGlmZmVyZW5jZXMgcG9zLXNvcyBpbiB2YWx1ZSBhbmQgZG95DQogICJTQVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsICJTQVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAjIERpZmZlcmVuY2VzIHBvcy1lb3MgaW4gdmFsdWUgYW5kIGRveQ0KICAiU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLCAiU0FWSV9kaWZmX2Vvc19wb3NfZG95IiwNCiAgIyBDYW5vcHkgaGVpZ2h0DQogICJjYW5vcHlfaGVpZ2h0IikNCmBgYA0KDQojIFJGIG1vZGVscyB3aXRoIG5vIHByZXZpb3VzIHZhbGlkYXRpb24NCg0KIyMgMDogUkYgd2l0aCBhbGwgcG9pbnRzDQoNClNwbGl0IGZpbHRlcmVkX2RhdGEwIGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykgDQp0cmFpbl9pbmRpY2VzMCA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGEwKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMCkpDQp0cmFpbl9kYXRhMCA8LSBmaWx0ZXJlZF9kYXRhMFt0cmFpbl9pbmRpY2VzMCwgXQ0KdGVzdF9kYXRhMCA8LSBmaWx0ZXJlZF9kYXRhMFstdHJhaW5faW5kaWNlczAsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMCAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJmMF9TMiA8LSBydW5fcmYodmFyc19SRiwgdHJhaW5fZGF0YTApDQpwcmludChyZjBfUzIkdGltZSkNCnNhdmUocmYwX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcmYwX1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KcHJlZGljdGlvbnNfcmYwX1MyIDwtIHByZWRpY3QocmYwX1MyJG1vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhMCxPT0IgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJyZXNwb25zZSIpDQpzYXZlKHByZWRpY3Rpb25zX3JmMF9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3ByZWRpY3Rpb25zX3JmMF9TMi5SZGF0YSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmYwX1MyLCB0ZXN0X2RhdGEwJEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmYwX1MyIDwtIGNvbXB1dGVfdmFyaW1wKHJmMF9TMiRtb2RlbCwgbnBlcm0gPSAxMDApDQpwcmludCh2YXJpbXBfcmYwX1MyJHRpbWUpDQpzYXZlKHZhcmltcF9yZjBfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC92YXJpbXBfcmYwX1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmMF9jb25kX1MyIDwtIGNvbXB1dGVfdmFyaW1wX2NvbmQocmYwX1MyJG1vZGVsLCBucGVybSA9IDEwMCkNCnByaW50KHZhcmltcF9yZjBfY29uZF9TMiR0aW1lKQ0Kc2F2ZSh2YXJpbXBfcmYwX2NvbmRfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC92YXJpbXBfcmYwX2NvbmRfUzIuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnBsb3QodmFyaW1wX3JmMF9TMiR2YXJpbXAsIG1hcmdpbiA9IGMoOSwgMywgMiwgMikpDQpwbG90KHZhcmltcF9yZjBfY29uZF9TMiR2YXJpbXAsIG1hcmdpbiA9IGMoOSwgMywgMiwgMikpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJvY19kYXRhMF9TMiA8LSBjb21wdXRlX3JvY19sZXZlbDEocmYwX1MyJG1vZGVsLCB0ZXN0X2RhdGEwKQ0KcHJpbnQocm9jX2RhdGEwX1MyJHRpbWUpDQpzYXZlKHJvY19kYXRhMF9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3JvY19kYXRhMF9TMi5SZGF0YSIpDQpgYGANCg0KYGBge3J9DQpyb2MwIDwtIGdncGxvdChyb2NfZGF0YTBfUzIkcm9jLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2MwDQpgYGANCg0KIyMgMTogUkYgd2l0aCBhbGwgR1BTIHBvaW50cyAoZGlmZiBvciBub3QpDQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTEgPC0gZGF0YV92YWxpZGF0aW9uICU+JQ0KICAjIFNlbGVjdCBvbmx5IEdQUyBwb2ludHMNCiAgZHBseXI6OmZpbHRlcihMY3RubXRoID09ICJMb2NhdGlvbiB3aXRoIEdQUyIgfA0KICAgICAgICAgICAgICAgICAgTGN0bm10aCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIikgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICAjIFJlbW92ZSBhbGwgcm93cyB3aXRoIHdyb25nIHZhbHVlcyBvZiBpbmRpY2VzIChub3QgYmV0d2VlbiAtMSBhbmQgMSkNCiAgZHBseXI6OmZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKSAlPiUNCiAgZHBseXI6OmZpbHRlcihORFZJX21heCA8PSAxKSAlPiUNCiAgZHBseXI6OmZpbHRlcihORE1JX21heCA8PSAxKSAlPiUNCiAgZHBseXI6OmZpbHRlcihORFdJX21pbiA+PSAtMSkgJT4lDQogICMgUmVtb3ZlIHJvd3Mgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbiBwcmVkaWN0b3JzDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGFsbF9vZih2YXJzX1JGKSwgfiAhaXMubmEoLikpKSAlPiUNCiAgIyBLZWVwIG9ubHkgcm93cyB3aXRoIGRpZmZlcmVuY2VzID4gMA0KICBkcGx5cjo6ZmlsdGVyKGlmX2FsbChjb250YWlucygiZGlmZiIpLCB+IC54ID4gMCkpICU+JQ0KICAjIFNlbGVjdCBvbmx5IHZhcmlhYmxlcyBuZWVkZWQNCiAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgYWxsX29mKHZhcnNfUkYpKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCnRyYWluX2luZGljZXMxIDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTEpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGExKSkNCnRyYWluX2RhdGExIDwtIGZpbHRlcmVkX2RhdGExW3RyYWluX2luZGljZXMxLCBdDQp0ZXN0X2RhdGExIDwtIGZpbHRlcmVkX2RhdGExWy10cmFpbl9pbmRpY2VzMSwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGExICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KcmYxX1MyIDwtIHJ1bl9yZih2YXJzX1JGLCB0cmFpbl9kYXRhMSwgIkVVTklTYV8xIikNCnByaW50KHJmMV9TMiR0aW1lKQ0Kc2F2ZShyZjFfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC9yZjFfUzIuUmRhdGEiKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpwcmVkaWN0aW9uc19yZjFfUzIgPC0gcHJlZGljdChyZjFfUzIkbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLE9PQiA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInJlc3BvbnNlIikNCnNhdmUocHJlZGljdGlvbnNfcmYxX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcHJlZGljdGlvbnNfcmYxX1MyLlJkYXRhIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZjFfUzIsIHRlc3RfZGF0YTEkRVVOSVNhXzEpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZjFfUzIgPC0gY29tcHV0ZV92YXJpbXAocmYxX1MyJG1vZGVsLCBucGVybSA9IDEwMCkNCnByaW50KHZhcmltcF9yZjFfUzIkdGltZSkNCnNhdmUodmFyaW1wX3JmMV9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3ZhcmltcF9yZjFfUzIuUmRhdGEiKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmYxX2NvbmRfUzIgPC0gY29tcHV0ZV92YXJpbXBfY29uZChyZjFfUzIkbW9kZWwsIG5wZXJtID0gMTAwKQ0KcHJpbnQodmFyaW1wX3JmMV9jb25kX1MyJHRpbWUpDQpzYXZlKHZhcmltcF9yZjFfY29uZF9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3ZhcmltcF9yZjFfY29uZF9TMi5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KcGxvdCh2YXJpbXBfcmYxX1MyJHZhcmltcCwgbWFyZ2luID0gYyg5LCAzLCAyLCAyKSkNCnBsb3QodmFyaW1wX3JmMV9jb25kX1MyJHZhcmltcCwgbWFyZ2luID0gYyg5LCAzLCAyLCAyKSkNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kcm9jX2RhdGExX1MyIDwtIGNvbXB1dGVfcm9jX2xldmVsMShyZjFfUzIkbW9kZWwsIHRlc3RfZGF0YTEpDQpwcmludChyb2NfZGF0YTFfUzIkdGltZSkNCnNhdmUocm9jX2RhdGExX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcm9jX2RhdGExX1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7cn0NCnJvYzEgPC0gZ2dwbG90KHJvY19kYXRhMV9TMiRyb2MsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzENCmBgYA0KDQojIFJvdWdoIHZhbGlkYXRpb24NCg0KRGVmaW5lIGEgc2V0IG9mIHJ1bGVzIGZvciBhIGZpcnN0IHZhbGlkYXRpb24gb2YgQUxMIFJlU3VydmV5IGRhdGEgKCJFeHBlcnQtYmFzZWQiIHJ1bGVzKS4gTm90IHZlcnkgYW1iaXRpb3VzLCBvbmx5IHRha2luZyBvdXQgb2JzZXJ2YXRpb25zIHRoYXQgYXJlIGNsZWFybHkgd3Jvbmcgb24gdGhlIGJhc2lzIG9mIGluZGljYXRvciB2YWx1ZXMuDQoNCkNyZWF0ZSBjb2x1bW4gZm9yIGZpcnN0IHZhbGlkYXRpb24gYmFzZWQgb24gZGlmZmVyZW50IGluZGljYXRvcnMsIHdoZXJlICJ3cm9uZyIgaXMgbm90ZWQgd2hlbiB0aGUgdmFsaWRhdGlvbiBydWxlIGlzIG5vdCBtZXQuIA0KDQpEZWZpbmUgcnVsZXM6DQoNCmBgYHtyfQ0KZGF0YV92YWxpZGF0aW9uIDwtDQogIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgbXV0YXRlKA0KICAgIHZhbGlkXzFfTkRXSSA9IGNhc2Vfd2hlbigNCiAgICAgICMgUG9pbnRzIHRoYXQgYXJlIGJhc2ljYWxseSB3YXRlcg0KICAgICAgTkRXSV9tYXggPiAwLjMgfiAid3JvbmciLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgIHZhbGlkXzFfQ0ggPSBjYXNlX3doZW4oDQogICAgICAjIFIgJiBRIHBvaW50cyB3aXRoIGhpZ2ggQ0gNCiAgICAgIEVVTklTYV8xICVpbiUgYygiUiIsICJRIikgJiBjYW5vcHlfaGVpZ2h0ID4gMiB+ICJ3cm9uZyIsDQogICAgICAjIFQgcG9pbnRzIHdpdGggbG93IENIDQogICAgICBFVU5JU2FfMSA9PSAiVCIgJiBjYW5vcHlfaGVpZ2h0IDwgMyB+ICJ3cm9uZyIsDQogICAgICAjIFMgcG9pbnRzIHdpdGggaGlnaCBDSA0KICAgICAgRVVOSVNhXzEgPT0gIlMiICYgY2Fub3B5X2hlaWdodCA+IDMgfiAid3JvbmciLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgICMgTm8gcnVsZXMgZm9yIE5EVkksIHdlIHdpbGwgdGFrZSBvdXQgb2JzZXJ2YXRpb25zIGJhc2VkIG9uIGRpc3RyaWJ1dGlvbnMNCiAgICAjIENvdW50IGhvdyBtYW55IHZhbGlkYXRpb24gcnVsZXMgYXJlIG5vdCBtZXQNCiAgICB2YWxpZF8xX2NvdW50ID0gcm93U3VtcyhhY3Jvc3MoYyh2YWxpZF8xX05EV0ksIHZhbGlkXzFfQ0gpLCB+IC4gPT0gIndyb25nIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEucm0gPSBUUlVFKSwNCiAgICAjIFBvaW50cyB3aGVyZSBhdCBsZWFzdCAxIHJ1bGUgbm90IG1ldA0KICAgIHZhbGlkXzEgPSBpZl9lbHNlKHZhbGlkXzFfY291bnQgPiAwLCAiQXQgbGVhc3QgMSBydWxlIGJyb2tlbiIsDQogICAgICAgICAgICAgICAgICAgICAgIk5vIHJ1bGVzIGJyb2tlbiBzbyBmYXIiKQ0KICAgICkNCmBgYA0KDQojIyMgUGxvdHMgcm91Z2ggdmFsaWRhdGlvbg0KDQpgYGB7cn0NCmdncGxvdChkYXRhX3ZhbGlkYXRpb24lPiUNCiAgICAgICAgIG11dGF0ZShydWxlc19icm9rZW4gPSBjYXNlX3doZW4oDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciIH4gIk5EV0kiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyIgfiAiQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRXSSArIENIIiwNCiAgICAgICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgICAgICAgICkpLCANCiAgICAgICBhZXMoeCA9IHZhbGlkXzFfY291bnQsIGZpbGwgPSBydWxlc19icm9rZW4pKSArDQogIGdlb21fYmFyKCkgKyBsYWJzKHggPSAiTnVtYmVyIG9mIGJyb2tlbiBydWxlcyIpDQpgYGANCg0KUHJvcG9ydGlvbiBvZiBvYnNlcnZhdGlvbnMgbm90IHZhbGlkYXRlZCAoc28gZmFyKToNCg0KYGBge3J9DQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUgZHBseXI6OmZpbHRlcih2YWxpZF8xX2NvdW50ID4gMCkpLw0KICBucm93KGRhdGFfdmFsaWRhdGlvbikNCmBgYA0KDQpCdXQgYmUgYXdhcmUgdGhhdCB0aGVyZSBhcmUgc3RpbGwgTUFOWSBtaXNzaW5nIFJTIGRhdGEuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICAgIG11dGF0ZShkaWZmX0dQUyA9IGlmX2Vsc2UoDQogICAgICAgICAgIExjdG5tdGggIT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgfA0KICAgICAgICAgICAgIGlzLm5hKExjdG5tdGgpLCAibm8iLCAieWVzIikpLCANCiAgICAgICBhZXMoeCA9IGRpZmZfR1BTLCBmaWxsID0gdmFsaWRfMSkpICsNCiAgZ2VvbV9iYXIoKSArIGxhYnMoeCA9ICJEaWZmZXJlbnRpYWwgR1BTIikNCmdncGxvdChkYXRhX3ZhbGlkYXRpb24gJT4lDQogICAgICAgICBtdXRhdGUoR1BTID0gY2FzZV93aGVuKA0KICAgICAgICAgICBMY3RubXRoID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIH4gInllcyIsDQogICAgICAgICAgIExjdG5tdGggPT0gIkxvY2F0aW9uIHdpdGggR1BTIiB+ICJ5ZXMiLA0KICAgICAgICAgICBpcy5uYShMY3RubXRoKSB+ICJubyIsDQogICAgICAgICAgIFRSVUUgfiAibm8iDQogICAgICAgICApKSwgDQogICAgICAgYWVzKHggPSBHUFMsIGZpbGwgPSB2YWxpZF8xKSkgKw0KICBnZW9tX2JhcigpICsgbGFicyh4ID0gIkdQUyIpDQpgYGANCg0KIyBSRiBtb2RlbHMgYWZ0ZXIgcm91Z2ggdmFsaWRhdGlvbg0KDQojIyAyOiBSRiB3aXRoIGFsbCBwb2ludHMgYWZ0ZXIgcm91Z2ggdmFsaWRhdGlvbg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEyIDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogICMgUmVtb3ZlIGFsbCByb3dzIHdpdGggd3JvbmcgdmFsdWVzIG9mIGluZGljZXMgKG5vdCBiZXR3ZWVuIC0xIGFuZCAxKQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EVklfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5ETUlfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EV0lfbWluID49IC0xKSAlPiUNCiAgIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluIHByZWRpY3RvcnMNCiAgZHBseXI6OmZpbHRlcihpZl9hbGwoYWxsX29mKHZhcnNfUkYpLCB+ICFpcy5uYSguKSkpICU+JQ0KICAjIEtlZXAgb25seSByb3dzIHdpdGggZGlmZmVyZW5jZXMgPiAwDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGNvbnRhaW5zKCJkaWZmIiksIH4gLnggPiAwKSkgJT4lDQogICMgRmlsdGVyIG91dCBwb2ludHMgdGhhdCBoYXZlIG5vdCBwYXNzZWQgdGhlIHJvdWdoIHZhbGlkYXRpb24NCiAgZmlsdGVyKHZhbGlkXzEgPT0gIk5vIHJ1bGVzIGJyb2tlbiBzbyBmYXIiKSAlPiUNCiAgIyBTZWxlY3Qgb25seSB2YXJpYWJsZXMgbmVlZGVkDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgRVVOSVNhXzEsIGFsbF9vZih2YXJzX1JGKSkgDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KdHJhaW5faW5kaWNlczIgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhMiksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTIpKQ0KdHJhaW5fZGF0YTIgPC0gZmlsdGVyZWRfZGF0YTJbdHJhaW5faW5kaWNlczIsIF0NCnRlc3RfZGF0YTIgPC0gZmlsdGVyZWRfZGF0YTJbLXRyYWluX2luZGljZXMyLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTIgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpyZjJfUzIgPC0gcnVuX3JmKHZhcnNfUkYsIHRyYWluX2RhdGEyLCAiRVVOSVNhXzEiKQ0KcHJpbnQocmYyX1MyJHRpbWUpDQpzYXZlKHJmMl9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3JmMl9TMi5SZGF0YSIpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnByZWRpY3Rpb25zX3JmMl9TMiA8LSBwcmVkaWN0KHJmMl9TMiRtb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YTIsT09CID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAicmVzcG9uc2UiKQ0Kc2F2ZShwcmVkaWN0aW9uc19yZjJfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC9wcmVkaWN0aW9uc19yZjJfUzIuUmRhdGEiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmMl9TMiwgdGVzdF9kYXRhMiRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmMl9TMiA8LSBjb21wdXRlX3ZhcmltcChyZjJfUzIkbW9kZWwsIG5wZXJtID0gMTAwKQ0Kc2F2ZSh2YXJpbXBfcmYyX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvdmFyaW1wX3JmMl9TMi5SZGF0YSIpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZjJfY29uZF9TMiA8LSBjb21wdXRlX3ZhcmltcF9jb25kKHJmMl9TMiRtb2RlbCwgbnBlcm0gPSAxMDApDQpwcmludCh2YXJpbXBfcmYyX2NvbmRfUzIkdGltZSkNCnNhdmUodmFyaW1wX3JmMl9jb25kX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvdmFyaW1wX3JmMl9jb25kX1MyLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQpwbG90KHZhcmltcF9yZjJfUzIkdmFyaW1wLCBtYXJnaW4gPSBjKDksIDMsIDIsIDIpKQ0KcGxvdCh2YXJpbXBfcmYyX2NvbmRfUzIkdmFyaW1wLCBtYXJnaW4gPSBjKDksIDMsIDIsIDIpKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpyb2NfZGF0YTJfUzIgPC0gY29tcHV0ZV9yb2NfbGV2ZWwxKHJmMl9TMiRtb2RlbCwgdGVzdF9kYXRhMikNCnNhdmUocm9jX2RhdGEyX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcm9jX2RhdGEyX1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7cn0NCnJvYzIgPC0gZ2dwbG90KHJvY19kYXRhMl9TMiRyb2MsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzINCmBgYA0KDQojIyAzOiBSRiB3aXRoIEdQUyBwb2ludHMgYWZ0ZXIgcm91Z2ggdmFsaWRhdGlvbg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEzIDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgIyBTZWxlY3Qgb25seSBHUFMgcG9pbnRzDQogIGRwbHlyOjpmaWx0ZXIoTGN0bm10aCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiIHwNCiAgICAgICAgICAgICAgICAgIExjdG5tdGggPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgIyBSZW1vdmUgYWxsIHJvd3Mgd2l0aCB3cm9uZyB2YWx1ZXMgb2YgaW5kaWNlcyAobm90IGJldHdlZW4gLTEgYW5kIDEpDQogIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRWSV9tYXggPD0gMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRNSV9tYXggPD0gMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRXSV9taW4gPj0gLTEpICU+JQ0KICAjIFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMgaW4gcHJlZGljdG9ycw0KICBkcGx5cjo6ZmlsdGVyKGlmX2FsbChhbGxfb2YodmFyc19SRiksIH4gIWlzLm5hKC4pKSkgJT4lDQogICMgS2VlcCBvbmx5IHJvd3Mgd2l0aCBkaWZmZXJlbmNlcyA+IDANCiAgZHBseXI6OmZpbHRlcihpZl9hbGwoY29udGFpbnMoImRpZmYiKSwgfiAueCA+IDApKSAlPiUNCiAgIyBGaWx0ZXIgb3V0IHBvaW50cyB0aGF0IGhhdmUgbm90IHBhc3NlZCB0aGUgcm91Z2ggdmFsaWRhdGlvbg0KICBmaWx0ZXIodmFsaWRfMSA9PSAiTm8gcnVsZXMgYnJva2VuIHNvIGZhciIpICU+JQ0KICAjIFNlbGVjdCBvbmx5IHZhcmlhYmxlcyBuZWVkZWQNCiAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgYWxsX29mKHZhcnNfUkYpKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCnRyYWluX2luZGljZXMzIDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTMpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGEzKSkNCnRyYWluX2RhdGEzIDwtIGZpbHRlcmVkX2RhdGEzW3RyYWluX2luZGljZXMzLCBdDQp0ZXN0X2RhdGEzIDwtIGZpbHRlcmVkX2RhdGEzWy10cmFpbl9pbmRpY2VzMywgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEzICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KcmYzX1MyIDwtIHJ1bl9yZih2YXJzX1JGLCB0cmFpbl9kYXRhMywgIkVVTklTYV8xIikNCnByaW50KHJmM19TMiR0aW1lKQ0Kc2F2ZShyZjNfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC9yZjNfUzIuUmRhdGEiKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpwcmVkaWN0aW9uc19yZjNfUzIgPC0gcHJlZGljdChyZjNfUzIkbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGEzLE9PQiA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInJlc3BvbnNlIikNCnNhdmUocHJlZGljdGlvbnNfcmYzX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcHJlZGljdGlvbnNfcmYzX1MyLlJkYXRhIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZjNfUzIsIHRlc3RfZGF0YTMkRVVOSVNhXzEpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZjNfUzIgPC0gY29tcHV0ZV92YXJpbXAocmYzX1MyJG1vZGVsLCBucGVybSA9IDEwMCkNCnNhdmUodmFyaW1wX3JmM19TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3ZhcmltcF9yZjNfUzIuUmRhdGEiKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmYzX2NvbmRfUzIgPC0gY29tcHV0ZV92YXJpbXBfY29uZChyZjNfUzIkbW9kZWwsIG5wZXJtID0gMTAwKQ0KcHJpbnQodmFyaW1wX3JmM19jb25kX1MyJHRpbWUpDQpzYXZlKHZhcmltcF9yZjNfY29uZF9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3ZhcmltcF9yZjNfY29uZF9TMi5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KcGxvdCh2YXJpbXBfcmYzX1MyJHZhcmltcCwgbWFyZ2luID0gYyg5LCAzLCAyLCAyKSkNCnBsb3QodmFyaW1wX3JmM19jb25kX1MyJHZhcmltcCwgbWFyZ2luID0gYyg5LCAzLCAyLCAyKSkNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kcm9jX2RhdGEzX1MyIDwtIGNvbXB1dGVfcm9jX2xldmVsMShyZjNfUzIkbW9kZWwsIHRlc3RfZGF0YTMpDQpzYXZlKHJvY19kYXRhM19TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3JvY19kYXRhM19TMi5SZGF0YSIpDQpgYGANCg0KYGBge3J9DQpyb2MzIDwtIGdncGxvdChyb2NfZGF0YTNfUzIkcm9jLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2MzDQpgYGANCg0KIyMgNDogUkYgKGxldmVsIDIpIHdpdGggR1BTIHBvaW50cyBhZnRlciByb3VnaCB2YWxpZGF0aW9uDQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTQgPC0gZGF0YV92YWxpZGF0aW9uICU+JQ0KICAjIFNlbGVjdCBvbmx5IEdQUyBwb2ludHMNCiAgZHBseXI6OmZpbHRlcihMY3RubXRoID09ICJMb2NhdGlvbiB3aXRoIEdQUyIgfA0KICAgICAgICAgICAgICAgICAgTGN0bm10aCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIikgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSksIEVVTklTYV8yID0gYXMuZmFjdG9yKEVVTklTYV8yKSkgJT4lDQogICMgUmVtb3ZlIGFsbCByb3dzIHdpdGggd3JvbmcgdmFsdWVzIG9mIGluZGljZXMgKG5vdCBiZXR3ZWVuIC0xIGFuZCAxKQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EVklfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5ETUlfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EV0lfbWluID49IC0xKSAlPiUNCiAgIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluIHByZWRpY3RvcnMNCiAgZHBseXI6OmZpbHRlcihpZl9hbGwoYWxsX29mKHZhcnNfUkYpLCB+ICFpcy5uYSguKSkpICU+JQ0KICAjIEtlZXAgb25seSByb3dzIHdpdGggZGlmZmVyZW5jZXMgPiAwDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGNvbnRhaW5zKCJkaWZmIiksIH4gLnggPiAwKSkgJT4lDQogICMgRmlsdGVyIG91dCBwb2ludHMgdGhhdCBoYXZlIG5vdCBwYXNzZWQgdGhlIHJvdWdoIHZhbGlkYXRpb24NCiAgZmlsdGVyKHZhbGlkXzEgPT0gIk5vIHJ1bGVzIGJyb2tlbiBzbyBmYXIiKSAlPiUNCiAgIyBGaWx0ZXIgb3V0IHBvaW50cyB3aGVyZSBFVU5JU2FfMiBpcyBOQQ0KICBmaWx0ZXIoIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICMgU2VsZWN0IG9ubHkgdmFyaWFibGVzIG5lZWRlZA0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIEVVTklTYV8xLCBFVU5JU2FfMiwgYWxsX29mKHZhcnNfUkYpKQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGE0ICU+JSBjb3VudChFVU5JU2FfMikNCmBgYA0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGE0IDwtIGZpbHRlcmVkX2RhdGE0ICU+JQ0KICAjIEtlZXAgb25seSBFVU5JU2FfMiBjYXRlZ29yaWVzIHdoZXJlIHRoZXJlIGFyZSBhdCBsZWFzdCA1MCBwb2ludHMNCiAgZmlsdGVyKEVVTklTYV8yICVpbiUNCiAgICAgICAgICAgKGZpbHRlcmVkX2RhdGE0ICU+JSBjb3VudChFVU5JU2FfMikgJT4lIGZpbHRlcihuID49IDUwKSkkRVVOSVNhXzIpICU+JQ0KICAjIERyb3AgdW51c2VkIGxldmVscyBvZiBFVU5JU2FfMg0KICBtdXRhdGUoRVVOSVNhXzIgPSBkcm9wbGV2ZWxzKEVVTklTYV8yKSkNCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhNCAlPiUgY291bnQoRVVOSVNhXzIpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KdHJhaW5faW5kaWNlczQgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhNCksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTQpKQ0KdHJhaW5fZGF0YTQgPC0gZmlsdGVyZWRfZGF0YTRbdHJhaW5faW5kaWNlczQsIF0NCnRlc3RfZGF0YTQgPC0gZmlsdGVyZWRfZGF0YTRbLXRyYWluX2luZGljZXM0LCBdDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJmNF9TMiA8LSBydW5fcmYodmFyc19SRiwgdHJhaW5fZGF0YTQsICJFVU5JU2FfMiIpDQpwcmludChyZjRfUzIkdGltZSkNCnNhdmUocmY0X1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcmY0X1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KcHJlZGljdGlvbnNfcmY0X1MyIDwtIHByZWRpY3QocmY0X1MyJG1vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhNCxPT0IgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJyZXNwb25zZSIpDQpzYXZlKHByZWRpY3Rpb25zX3JmNF9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3ByZWRpY3Rpb25zX3JmNF9TMi5SZGF0YSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmY0X1MyLCB0ZXN0X2RhdGE0JEVVTklTYV8yKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmY0X1MyIDwtIGNvbXB1dGVfdmFyaW1wKHJmNF9TMiRtb2RlbCwgbnBlcm0gPSAxMDApDQpzYXZlKHZhcmltcF9yZjRfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC92YXJpbXBfcmY0X1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmNF9jb25kX1MyIDwtIGNvbXB1dGVfdmFyaW1wX2NvbmQocmY0X1MyJG1vZGVsLCBucGVybSA9IDEwMCkNCnByaW50KHZhcmltcF9yZjRfY29uZF9TMiR0aW1lKQ0Kc2F2ZSh2YXJpbXBfcmY0X2NvbmRfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC92YXJpbXBfcmY0X2NvbmRfUzIuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnBsb3QodmFyaW1wX3JmNF9TMiR2YXJpbXAsIG1hcmdpbiA9IGMoOSwgMywgMiwgMikpDQpwbG90KHZhcmltcF9yZjRfY29uZF9TMiR2YXJpbXAsIG1hcmdpbiA9IGMoOSwgMywgMiwgMikpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJvY19kYXRhNF9TMiA8LSBjb21wdXRlX3JvY19sZXZlbDIocmY0X1MyJG1vZGVsLCB0ZXN0X2RhdGE0KQ0Kc2F2ZShyb2NfZGF0YTRfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC9yb2NfZGF0YTRfUzIuUmRhdGEiKQ0KYGBgDQoNCmBgYHtyfQ0Kcm9jNCA8LSBnZ3Bsb3Qocm9jX2RhdGE0X1MyJHJvYywgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jNA0KYGBgDQoNCiMjIDU6IFJGIHdpdGggR1BTIHBvaW50cyBhZnRlciByb3VnaCB2YWxpZGF0aW9uLCB1bmNvcnJlbGF0ZWQgcHJlZGljdG9ycw0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGE1IDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgIyBTZWxlY3Qgb25seSBHUFMgcG9pbnRzDQogIGRwbHlyOjpmaWx0ZXIoTGN0bm10aCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiIHwNCiAgICAgICAgICAgICAgICAgIExjdG5tdGggPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgIyBSZW1vdmUgYWxsIHJvd3Mgd2l0aCB3cm9uZyB2YWx1ZXMgb2YgaW5kaWNlcyAobm90IGJldHdlZW4gLTEgYW5kIDEpDQogIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRWSV9tYXggPD0gMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRNSV9tYXggPD0gMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRXSV9taW4gPj0gLTEpICU+JQ0KICAjIFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMgaW4gcHJlZGljdG9ycw0KICBkcGx5cjo6ZmlsdGVyKGlmX2FsbChhbGxfb2YodmFyc19SRl91bmNvcnIpLCB+ICFpcy5uYSguKSkpICU+JQ0KICAjIEtlZXAgb25seSByb3dzIHdpdGggZGlmZmVyZW5jZXMgPiAwDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGNvbnRhaW5zKCJkaWZmIiksIH4gLnggPiAwKSkgJT4lDQogICMgRmlsdGVyIG91dCBwb2ludHMgdGhhdCBoYXZlIG5vdCBwYXNzZWQgdGhlIHJvdWdoIHZhbGlkYXRpb24NCiAgZHBseXI6OmZpbHRlcih2YWxpZF8xID09ICJObyBydWxlcyBicm9rZW4gc28gZmFyIikgJT4lDQogICMgU2VsZWN0IG9ubHkgdmFyaWFibGVzIG5lZWRlZA0KICBkcGx5cjo6c2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgYWxsX29mKHZhcnNfUkZfdW5jb3JyKSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQp0cmFpbl9pbmRpY2VzNSA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGE1KSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhNSkpDQp0cmFpbl9kYXRhNSA8LSBmaWx0ZXJlZF9kYXRhM1t0cmFpbl9pbmRpY2VzNSwgXQ0KdGVzdF9kYXRhNSA8LSBmaWx0ZXJlZF9kYXRhM1stdHJhaW5faW5kaWNlczUsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhNSAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJmNV9TMiA8LSBydW5fcmYodmFyc19SRl91bmNvcnIsIHRyYWluX2RhdGE1LCAiRVVOSVNhXzEiKQ0KcHJpbnQocmY1X1MyJHRpbWUpDQpzYXZlKHJmNV9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3JmNV9TMi5SZGF0YSIpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnByZWRpY3Rpb25zX3JmNV9TMiA8LSBwcmVkaWN0KHJmNV9TMiRtb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YTUsT09CID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAicmVzcG9uc2UiKQ0Kc2F2ZShwcmVkaWN0aW9uc19yZjVfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC9wcmVkaWN0aW9uc19yZjVfUzIuUmRhdGEiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmNV9TMiwgdGVzdF9kYXRhNSRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmNV9TMiA8LSBjb21wdXRlX3ZhcmltcChyZjVfUzIkbW9kZWwsIG5wZXJtID0gMTAwKQ0Kc2F2ZSh2YXJpbXBfcmY1X1MyLCBmaWxlID0gIm9iamVjdHMvMTAvdmFyaW1wX3JmNV9TMi5SZGF0YSIpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZjVfY29uZF9TMiA8LSBjb21wdXRlX3ZhcmltcF9jb25kKHJmNV9TMiRtb2RlbCwgbnBlcm0gPSAxMDApDQpwcmludCh2YXJpbXBfcmY1X2NvbmRfUzIkdGltZSkNCnNhdmUodmFyaW1wX3JmNV9jb25kX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvdmFyaW1wX3JmNV9jb25kX1MyLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQpwbG90KHZhcmltcF9yZjVfUzIkdmFyaW1wLCBtYXJnaW4gPSBjKDksIDMsIDIsIDIpKQ0KcGxvdCh2YXJpbXBfcmY1X2NvbmRfUzIkdmFyaW1wLCBtYXJnaW4gPSBjKDksIDMsIDIsIDIpKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpyb2NfZGF0YTVfUzIgPC0gY29tcHV0ZV9yb2NfbGV2ZWwxKHJmNV9TMiRtb2RlbCwgdGVzdF9kYXRhNSkNCnNhdmUocm9jX2RhdGE1X1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcm9jX2RhdGE1X1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7cn0NCnJvYzUgPC0gZ2dwbG90KHJvY19kYXRhNV9TMiRyb2MsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzUNCmBgYA0KDQojIFJGIG1tb2RlbHMgYWZ0ZXIgdHJhaW5pbmcgZGF0YSByZWZpbmVtZW50DQoNCiMjIDY6IEdQUyBwb2ludHMsIHdpdGhpbiAxMC05MHRoIHBlcmNlbnRpbGUNCg0KUmVmaW5lbWVudCBiYXNlZCBvbiB0aGUgdmFyaWFibGVzOiBTQVZJX3Bvc192YWx1ZSwgTkRXSV9taW4sIE5ETUlfbWluLCBORE1JX21heCAobGF0ZXIgY2hlY2sgdmFyaWFibGUgaW1wb3J0YW5jZXMgd2VsbCEpLg0KDQpEaXN0cmlidXRpb24gcGxvdHM6DQoNCmBgYHtyfQ0KZGlzdHJfcGxvdF9wZXJjZW50aWxlcyA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhcnMsIHlfbGFiZWxzKSB7DQogIGZvciAoaSBpbiBzZXFfYWxvbmcoeV92YXJzKSkgew0KICAgIHlfdmFyIDwtIHlfdmFyc1tbaV1dDQogICAgeV9sYWJlbCA8LSB5X2xhYmVsc1tbaV1dDQogICAgDQogICAgIyBDYWxjdWxhdGUgcGVyY2VudGlsZXMgcGVyIEVVTklTYV8xIGdyb3VwDQogICAgcGVyY2VudGlsZXMgPC0gZGF0YSAlPiUNCiAgICAgIGdyb3VwX2J5KEVVTklTYV8xKSAlPiUNCiAgICAgIHN1bW1hcmlzZSgNCiAgICAgICAgcDEwID0gcXVhbnRpbGUoLmRhdGFbW3lfdmFyXV0sIDAuMSwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgcDkwID0gcXVhbnRpbGUoLmRhdGFbW3lfdmFyXV0sIDAuOSwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgLmdyb3VwcyA9ICJkcm9wIg0KICAgICAgKQ0KICAgIA0KICAgICMgSm9pbiBwZXJjZW50aWxlcyBiYWNrIHRvIGRhdGENCiAgICBkYXRhX2ZsYWdnZWQgPC0gZGF0YSAlPiUNCiAgICAgIGxlZnRfam9pbihwZXJjZW50aWxlcywgYnkgPSAiRVVOSVNhXzEiKSAlPiUNCiAgICAgIG11dGF0ZShvdXRsaWVyX2ZsYWcgPSBjYXNlX3doZW4oDQogICAgICAgIC5kYXRhW1t5X3Zhcl1dIDwgcDEwIH4gImxvdyIsDQogICAgICAgIC5kYXRhW1t5X3Zhcl1dID4gcDkwIH4gImhpZ2giLA0KICAgICAgICBUUlVFIH4gIm1pZCINCiAgICAgICkpDQogICAgDQogICAgIyBGaWx0ZXIgYW5kIHBsb3QNCiAgICBwIDwtIGdncGxvdChkYXRhID0gZGF0YV9mbGFnZ2VkICU+JQ0KICAgICAgICAgICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSwNCiAgICAgICAgICAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gLmRhdGFbW3lfdmFyXV0pKSArDQogICAgICBnZW9tX2ZsYXRfdmlvbGluKGFlcyhmaWxsID0gRVVOSVNhXzFfZGVzY3IpLA0KICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgpICsNCiAgICAgIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gaWZlbHNlKG91dGxpZXJfZmxhZyA9PSAibWlkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVVTklTYV8xX2Rlc2NyLCAiZ3JleSIpKSwNCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsDQogICAgICAgICAgICAgICAgIGFscGhhID0gMC42KSArDQogICAgICBnZW9tX2JveHBsb3QoYWVzKGZpbGwgPSBFVU5JU2FfMV9kZXNjciksIHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsDQogICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4gPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4LCBuYS5ybSA9IFRSVUUpICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAwLjEsIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICAgICAgbGFicyh5ID0geV9sYWJlbCwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICAgICAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICAgICAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICAgICAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KICAgIA0KICAgIHByaW50KHApDQogIH0NCn0NCmBgYA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3RfcGVyY2VudGlsZXMoZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICMgR2V0IEdQUyBwb2ludHMgYWZ0ZXIgcm91Z2ggdmFsaWRhdGlvbg0KICAgICAgICAgICAgIGZpbHRlcihQbG90T2JzZXJ2YXRpb25JRCAlaW4lIGZpbHRlcmVkX2RhdGEzJFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICAgICAgICAgYygiY2Fub3B5X2hlaWdodCIsICJTQVZJX3Bvc192YWx1ZSIsICJORFdJX21pbiIsICJORE1JX21pbiIsIA0KICAgICAgICAgICAgICJORE1JX21heCIpLA0KICAgICAgICAgICBjKCJjYW5vcHlfaGVpZ2h0IiwgIlNBVklfcG9zX3ZhbHVlIiwgIk5EV0lfbWluIiwgIk5ETUlfbWluIiwgDQogICAgICAgICAgICAgIk5ETUlfbWF4IikpDQpgYGANCg0KU28gZmFyIG5vdCB1c2luZyBjYW5vcHlfaGVpZ2h0IGZvciByZWZpbmVtZW50Lg0KDQpDYWxjdWxhdGUgcGVyY2VudGlsZXM6DQoNCmBgYHtyfQ0KcGVyY2VudGlsZXM2IDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgIyBHZXQgR1BTIHBvaW50cyBhZnRlciByb3VnaCB2YWxpZGF0aW9uDQogIGZpbHRlcihQbG90T2JzZXJ2YXRpb25JRCAlaW4lIGZpbHRlcmVkX2RhdGEzJFBsb3RPYnNlcnZhdGlvbklEKSAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUoDQogICAgcGVyY2VudGlsZV8xMF9TQVZJX3Bvc192YWx1ZSA9IHF1YW50aWxlKFNBVklfcG9zX3ZhbHVlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYnMgPSAwLjEwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfMjBfU0FWSV9wb3NfdmFsdWUgPSBxdWFudGlsZShTQVZJX3Bvc192YWx1ZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzgwX1NBVklfcG9zX3ZhbHVlID0gcXVhbnRpbGUoU0FWSV9wb3NfdmFsdWUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDAuODAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV85MF9TQVZJX3Bvc192YWx1ZSA9IHF1YW50aWxlKFNBVklfcG9zX3ZhbHVlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDAuOTAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV8xMF9ORFdJX21pbiA9IHF1YW50aWxlKE5EV0lfbWluLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYnMgPSAwLjEwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfMjBfTkRXSV9taW4gPSBxdWFudGlsZShORFdJX21pbiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzgwX05EV0lfbWluID0gcXVhbnRpbGUoTkRXSV9taW4sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDAuODAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV85MF9ORFdJX21pbiA9IHF1YW50aWxlKE5EV0lfbWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDAuOTAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV8xMF9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYnMgPSAwLjEwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gPSBxdWFudGlsZShORE1JX21pbiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzgwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDAuODAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV85MF9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDAuOTAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV8xMF9ORE1JX21heCA9IHF1YW50aWxlKE5ETUlfbWF4LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYnMgPSAwLjEwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfMjBfTkRNSV9tYXggPSBxdWFudGlsZShORE1JX21heCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzgwX05ETUlfbWF4ID0gcXVhbnRpbGUoTkRNSV9tYXgsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDAuODAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV85MF9ORE1JX21heCA9IHF1YW50aWxlKE5ETUlfbWF4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDAuOTAsIG5hLnJtID0gVCkNCiAgICAgICAgICAgICkNCmBgYA0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGE2IDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgIyBHZXQgR1BTIHBvaW50cyBhZnRlciByb3VnaCB2YWxpZGF0aW9uDQogIGZpbHRlcihQbG90T2JzZXJ2YXRpb25JRCAlaW4lIGZpbHRlcmVkX2RhdGEzJFBsb3RPYnNlcnZhdGlvbklEKSAlPiUNCiAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgYWxsX29mKHZhcnNfUkYpKSAlPiUNCiAgbGVmdF9qb2luKHBlcmNlbnRpbGVzNiwgYnkgPSAiRVVOSVNhXzEiKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcigNCiAgICAoU0FWSV9wb3NfdmFsdWUgPj0gcGVyY2VudGlsZV8xMF9TQVZJX3Bvc192YWx1ZSAmDQogICAgICAgU0FWSV9wb3NfdmFsdWUgPD0gcGVyY2VudGlsZV85MF9TQVZJX3Bvc192YWx1ZSkgJg0KICAgICAgKE5EV0lfbWluID49IHBlcmNlbnRpbGVfMTBfTkRXSV9taW4gJg0KICAgICAgICAgTkRXSV9taW4gPD0gcGVyY2VudGlsZV85MF9ORFdJX21pbikgJg0KICAgICAgKE5ETUlfbWluID49IHBlcmNlbnRpbGVfMTBfTkRNSV9taW4gJg0KICAgICAgICAgTkRNSV9taW4gPD0gcGVyY2VudGlsZV85MF9ORE1JX21pbikgJg0KICAgICAgKE5ETUlfbWF4ID49IHBlcmNlbnRpbGVfMTBfTkRNSV9tYXggJg0KICAgICAgICAgTkRNSV9tYXggPD0gcGVyY2VudGlsZV85MF9ORE1JX21heCkNCiAgICApDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KdHJhaW5faW5kaWNlczYgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhNiksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTYpKQ0KdHJhaW5fZGF0YTYgPC0gZmlsdGVyZWRfZGF0YTZbdHJhaW5faW5kaWNlczYsIF0NCnRlc3RfZGF0YTYgPC0gZmlsdGVyZWRfZGF0YTZbLXRyYWluX2luZGljZXM2LCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTYgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpyZjZfUzIgPC0gcnVuX3JmKHZhcnNfUkYsIHRyYWluX2RhdGE2LCAiRVVOSVNhXzEiKQ0KcHJpbnQocmY2X1MyJHRpbWUpDQpzYXZlKHJmNl9TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3JmNl9TMi5SZGF0YSIpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnByZWRpY3Rpb25zX3JmNl9TMiA8LSBwcmVkaWN0KHJmNl9TMiRtb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YTYsT09CID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAicmVzcG9uc2UiKQ0Kc2F2ZShwcmVkaWN0aW9uc19yZjZfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC9wcmVkaWN0aW9uc19yZjZfUzIuUmRhdGEiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmNl9TMiwgdGVzdF9kYXRhNiRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmNl9TMiA8LSBjb21wdXRlX3ZhcmltcChyZjZfUzIkbW9kZWwsIG5wZXJtID0gMTAwKQ0Kc2F2ZSh2YXJpbXBfcmY2X1MyLCBmaWxlID0gIm9iamVjdHMvMTAvdmFyaW1wX3JmNl9TMi5SZGF0YSIpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZjZfY29uZF9TMiA8LSBjb21wdXRlX3ZhcmltcF9jb25kKHJmNl9TMiRtb2RlbCwgbnBlcm0gPSAxMDApDQpwcmludCh2YXJpbXBfcmY2X2NvbmRfUzIkdGltZSkNCnNhdmUodmFyaW1wX3JmNl9jb25kX1MyLCBmaWxlID0gIm9iamVjdHMvMTAvdmFyaW1wX3JmNl9jb25kX1MyLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQpwbG90KHZhcmltcF9yZjZfUzIkdmFyaW1wLCBtYXJnaW4gPSBjKDksIDMsIDIsIDIpKQ0KcGxvdCh2YXJpbXBfcmY2X2NvbmRfUzIkdmFyaW1wLCBtYXJnaW4gPSBjKDksIDMsIDIsIDIpKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpyb2NfZGF0YTZfUzIgPC0gY29tcHV0ZV9yb2NfbGV2ZWwxKHJmNl9TMiRtb2RlbCwgdGVzdF9kYXRhNikNCnNhdmUocm9jX2RhdGE2X1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcm9jX2RhdGE2X1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7cn0NCnJvYzYgPC0gZ2dwbG90KHJvY19kYXRhNl9TMiRyb2MsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzYNCmBgYA0KDQojIyA3OiBHUFMgcG9pbnRzLCB3aXRoaW4gMjAtODB0aCBwZXJjZW50aWxlDQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTcgPC0gZGF0YV92YWxpZGF0aW9uICU+JQ0KICAjIEdldCBHUFMgcG9pbnRzIGFmdGVyIHJvdWdoIHZhbGlkYXRpb24NCiAgZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEICVpbiUgZmlsdGVyZWRfZGF0YTMkUGxvdE9ic2VydmF0aW9uSUQpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIEVVTklTYV8xLCBhbGxfb2YodmFyc19SRikpICU+JQ0KICBsZWZ0X2pvaW4ocGVyY2VudGlsZXM2LCBieSA9ICJFVU5JU2FfMSIpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgZmlsdGVyKA0KICAgIChTQVZJX3Bvc192YWx1ZSA+PSBwZXJjZW50aWxlXzIwX1NBVklfcG9zX3ZhbHVlICYNCiAgICAgICBTQVZJX3Bvc192YWx1ZSA8PSBwZXJjZW50aWxlXzgwX1NBVklfcG9zX3ZhbHVlKSAmDQogICAgICAoTkRXSV9taW4gPj0gcGVyY2VudGlsZV8yMF9ORFdJX21pbiAmDQogICAgICAgICBORFdJX21pbiA8PSBwZXJjZW50aWxlXzgwX05EV0lfbWluKSAmDQogICAgICAoTkRNSV9taW4gPj0gcGVyY2VudGlsZV8yMF9ORE1JX21pbiAmDQogICAgICAgICBORE1JX21pbiA8PSBwZXJjZW50aWxlXzgwX05ETUlfbWluKSAmDQogICAgICAoTkRNSV9tYXggPj0gcGVyY2VudGlsZV8yMF9ORE1JX21heCAmDQogICAgICAgICBORE1JX21heCA8PSBwZXJjZW50aWxlXzgwX05ETUlfbWF4KQ0KICAgICkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQp0cmFpbl9pbmRpY2VzNyA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGE3KSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhNykpDQp0cmFpbl9kYXRhNyA8LSBmaWx0ZXJlZF9kYXRhN1t0cmFpbl9pbmRpY2VzNywgXQ0KdGVzdF9kYXRhNyA8LSBmaWx0ZXJlZF9kYXRhN1stdHJhaW5faW5kaWNlczcsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhNyAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJmN19TMiA8LSBydW5fcmYodmFyc19SRiwgdHJhaW5fZGF0YTcsICJFVU5JU2FfMSIpDQpwcmludChyZjdfUzIkdGltZSkNCnNhdmUocmY3X1MyLCBmaWxlID0gIm9iamVjdHMvMTAvcmY3X1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KcHJlZGljdGlvbnNfcmY3X1MyIDwtIHByZWRpY3QocmY3X1MyJG1vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhNyxPT0IgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJyZXNwb25zZSIpDQpzYXZlKHByZWRpY3Rpb25zX3JmN19TMiwgZmlsZSA9ICJvYmplY3RzLzEwL3ByZWRpY3Rpb25zX3JmN19TMi5SZGF0YSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmY3X1MyLCB0ZXN0X2RhdGE3JEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmY3X1MyIDwtIGNvbXB1dGVfdmFyaW1wKHJmN19TMiRtb2RlbCwgbnBlcm0gPSAxMDApDQpzYXZlKHZhcmltcF9yZjdfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC92YXJpbXBfcmY3X1MyLlJkYXRhIikNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmN19jb25kX1MyIDwtIGNvbXB1dGVfdmFyaW1wX2NvbmQocmY3X1MyJG1vZGVsLCBucGVybSA9IDEwMCkNCnByaW50KHZhcmltcF9yZjdfY29uZF9TMiR0aW1lKQ0Kc2F2ZSh2YXJpbXBfcmY3X2NvbmRfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC92YXJpbXBfcmY3X2NvbmRfUzIuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnBsb3QodmFyaW1wX3JmN19TMiR2YXJpbXAsIG1hcmdpbiA9IGMoOSwgMywgMiwgMikpDQpwbG90KHZhcmltcF9yZjdfY29uZF9TMiR2YXJpbXAsIG1hcmdpbiA9IGMoOSwgMywgMiwgMikpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJvY19kYXRhN19TMiA8LSBjb21wdXRlX3JvY19sZXZlbDEocmY3X1MyJG1vZGVsLCB0ZXN0X2RhdGE3KQ0Kc2F2ZShyb2NfZGF0YTdfUzIsIGZpbGUgPSAib2JqZWN0cy8xMC9yb2NfZGF0YTdfUzIuUmRhdGEiKQ0KYGBgDQoNCmBgYHtyfQ0Kcm9jNyA8LSBnZ3Bsb3Qocm9jX2RhdGE3X1MyJHJvYywgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jNg0KYGBgDQoNCg0KDQoNCiMgT0xEIGZyb20gaGVyZQ0KDQojIyBNYXBzDQoNCiMjIyBQb2ludHMgR1BTDQoNCmBgYHtyfQ0KIyBMb2FkIHdvcmxkIGJvdW5kYXJpZXMNCndvcmxkIDwtIG5lX2NvdW50cmllcyhzY2FsZSA9ICJtZWRpdW0iLCByZXR1cm5jbGFzcyA9ICJzZiIpDQoNCiMgQ2FsY3VsYXRlIHRoZSBleHRlbnQgb2YgdGhlIHBvaW50cw0KcG9pbnRzX0dQU19leHRlbnQgPC0gZGF0YV92YWxpZGF0aW9uICU+JQ0KICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgZHBseXI6OmZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICBkcGx5cjo6ZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwNCiAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIikgJT4lDQogIHN1bW1hcmlzZShsb25fbWluID0gbWluKExvbl91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbG9uX21heCA9IG1heChMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxhdF9taW4gPSBtaW4oTGF0X3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWF4ID0gbWF4KExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpKQ0KDQojIEFkZCBwYWRkaW5nIHRvIHRoZSBleHRlbnQgKGFkanVzdCBhcyBuZWVkZWQpDQpwYWRkaW5nIDwtIDIgICMgQWRqdXN0IHBhZGRpbmcgdG8geW91ciBwcmVmZXJlbmNlDQp4X2xpbWl0cyA8LSBjKHBvaW50c19HUFNfZXh0ZW50JGxvbl9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfR1BTX2V4dGVudCRsb25fbWF4ICsgcGFkZGluZykNCnlfbGltaXRzIDwtIGMocG9pbnRzX0dQU19leHRlbnQkbGF0X21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19HUFNfZXh0ZW50JGxhdF9tYXggKyBwYWRkaW5nKQ0KDQojIENyZWF0ZSB0aGUgem9vbWVkIG1hcA0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB3b3JsZCwgZmlsbCA9ICJsaWdodGJsdWUiLCBjb2xvciA9ICJncmF5IikgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBkYXRhX3ZhbGlkYXRpb24gJT4lDQogICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgfA0KICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiKSwNCiAgICAgICAgICAgICBhZXMoeCA9IExvbl91cGRhdGVkLCB5ID0gTGF0X3VwZGF0ZWQsIGNvbG9yID0gRVVOSVNhXzEpLA0KICAgICAgICAgICAgIHNpemUgPSAxKSArDQogIGNvb3JkX3NmKHhsaW0gPSB4X2xpbWl0cywgeWxpbSA9IHlfbGltaXRzKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCk51bWJlciBvZiBHUFMgcG9pbnRzIGJ5IENvdW50cnk6DQoNCmBgYHtyfQ0KZGF0YV92YWxpZGF0aW9uICU+JQ0KICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgZHBseXI6OmZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICBkcGx5cjo6ZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwNCiAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIikgJT4lDQogIGNvdW50KENvdW50cnkpDQpgYGANCg0KIyMjIFBvaW50cyBSZVN1cnZleQ0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIHRoZSBleHRlbnQgb2YgdGhlIHBvaW50cw0KcG9pbnRzX3Jlc3VydmV5X2V4dGVudCA8LSBkYXRhX3ZhbGlkYXRpb24gJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogIHN1bW1hcmlzZShsb25fbWluID0gbWluKExvbl91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbG9uX21heCA9IG1heChMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxhdF9taW4gPSBtaW4oTGF0X3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWF4ID0gbWF4KExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpKQ0KDQojIEFkZCBwYWRkaW5nIHRvIHRoZSBleHRlbnQgKGFkanVzdCBhcyBuZWVkZWQpDQpwYWRkaW5nIDwtIDIgICMgQWRqdXN0IHBhZGRpbmcgdG8geW91ciBwcmVmZXJlbmNlDQp4X2xpbWl0cyA8LSBjKHBvaW50c19yZXN1cnZleV9leHRlbnQkbG9uX21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19yZXN1cnZleV9leHRlbnQkbG9uX21heCArIHBhZGRpbmcpDQp5X2xpbWl0cyA8LSBjKHBvaW50c19yZXN1cnZleV9leHRlbnQkbGF0X21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19yZXN1cnZleV9leHRlbnQkbGF0X21heCArIHBhZGRpbmcpDQoNCiMgQ3JlYXRlIHRoZSB6b29tZWQgbWFwDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHdvcmxkLCBmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImdyYXkiKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApLA0KICAgICAgICAgICAgIGFlcyh4ID0gTG9uX3VwZGF0ZWQsIHkgPSBMYXRfdXBkYXRlZCwgY29sb3IgPSBFVU5JU2FfMSksDQogICAgICAgICAgICAgc2l6ZSA9IDEpICsNCiAgY29vcmRfc2YoeGxpbSA9IHhfbGltaXRzLCB5bGltID0geV9saW1pdHMpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KTnVtYmVyIG9mIFJlU3VydmV5IHBvaW50cyBieSBDb3VudHJ5Og0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgY291bnQoQ291bnRyeSkNCmBgYA0KDQojIyBDb3JkaWxsZXJhIGRhdGENCg0KYGBge3J9DQpBbHBpbmVHcmFzc2xhbmRzX2luZGljZXMgPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvQWxwaW5lR3Jhc3NsYW5kcy9BbHBpbmVHcmFzc2xhbmRfU2VudGluZWxfUGxvdF9BbGx5ZWFyX0FsbG1ldHJpY3MuY3N2IikNCkFscGluZUdyYXNzbGFuZHNfcGhlbiA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9BbHBpbmVHcmFzc2xhbmRzL0FscGluZUdyYXNzbGFuZHNfUGhlbm9sb2d5X1NPU19FT1NfUGVha19ORFZJX0FtcGxpdHVkZS5jc3YiKQ0KQWxwaW5lR3Jhc3NsYW5kc19DSCA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9BbHBpbmVHcmFzc2xhbmRzL0FscGluZUdyYXNzbGFuZHNfQ2Fub3B5SGVpZ2h0XzFtLmNzdiIpDQpWZWdldGF0aW9uVHlwZXNfaW5kaWNlcyA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9WZWdldGF0aW9uVHlwZXMvVmVnZXRhdGlvblR5cGVzX1NlbnRpbmVsX1Bsb3RfQWxsWWVhcl9BbGxtZXRyaWNzLmNzdiIpDQpWZWdldGF0aW9uVHlwZXNfcGhlbiA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9WZWdldGF0aW9uVHlwZXMvVmVnZXRhdGlvblR5cGVzX1BoZW5vbG9neV9TT1NfRU9TX1BlYWtfTkRWSV9BbXBsaXR1ZGUuY3N2IikNClZlZ2V0YXRpb25UeXBlc19DSCA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9WZWdldGF0aW9uVHlwZXMvVmVnZXRhdGlvblR5cGVzX0Nhbm9weUhlaWdodF8xbS5jc3YiKQ0KYGBgDQoNCmBgYHtyfQ0KQWxwaW5lR3Jhc3NsYW5kcyA8LSBBbHBpbmVHcmFzc2xhbmRzX2luZGljZXMgJT4lDQogIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvLCAtTG9jYWxpZGFkKSAlPiUNCiAgcmVuYW1lKEjDoWJpdGF0ID0gIkjvv71iaXRhdCIpICU+JSANCiAgZnVsbF9qb2luKEFscGluZUdyYXNzbGFuZHNfcGhlbiAgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvLCAtTG9jYWxpZGFkKSAlPiUNCiAgICAgICAgICAgICAgcmVuYW1lKEjDoWJpdGF0ID0gIkjvv71iaXRhdCIpKSAlPiUNCiAgZnVsbF9qb2luKEFscGluZUdyYXNzbGFuZHNfQ0ggICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbywgLUxvY2FsaWRhZCkpICU+JQ0KICBzZWxlY3QoLURhdGVfX3llYXIsIC0gYFByZWNpc2nvv71uYCkgJT4lDQogIG11dGF0ZShEQVRFID0geW1kKERBVEUpKSAlPiUNCiAgcmVuYW1lKElEID0gIlJlbGV2ZV9udW0iKSAlPiUNCiAgbXV0YXRlKElEID0gYXMuY2hhcmFjdGVyKElEKSkgJT4lDQogIG11dGF0ZShsYXllciA9ICJBbHBpbmVHcmFzc2xhbmRzIikNCmBgYA0KDQpgYGB7cn0NClZlZ2V0YXRpb25UeXBlcyA8LSBWZWdldGF0aW9uVHlwZXNfaW5kaWNlcyAlPiUNCiAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8pICU+JQ0KICBmdWxsX2pvaW4oVmVnZXRhdGlvblR5cGVzX3BoZW4gICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbykpICU+JQ0KICBmdWxsX2pvaW4oVmVnZXRhdGlvblR5cGVzX0NIICAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8pKSAlPiUNCiAgcmVuYW1lKEjDoWJpdGF0ID0gIlRZUEUiKSAlPiUNCiAgbXV0YXRlKGxheWVyID0gIlZlZ2V0YXRpb25UeXBlcyIpDQpgYGANCg0KTWVyZ2UgYm90aCBkYXRhc2V0czoNCg0KYGBge3J9DQpjb3JkaWxsZXJhIDwtIGJpbmRfcm93cygNCiAgQWxwaW5lR3Jhc3NsYW5kcyAlPiUgc2VsZWN0KERBVEUsIElELCBzdGFydHNfd2l0aCgiTkRNSSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRzX3dpdGgoIk5EVkkiKSwgSMOhYml0YXQsICJFT1NfRE9ZIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQZWFrX0RPWSIsICJTT1NfRE9ZIiwgIlNlYXNvbl9MZW5ndGgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNhbm9weV9oZWlnaHQiLCAibGF5ZXIiKSwNCiAgVmVnZXRhdGlvblR5cGVzICU+JSBzZWxlY3QoREFURSwgSUQsIHN0YXJ0c193aXRoKCJORE1JIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFydHNfd2l0aCgiTkRWSSIpLCBIw6FiaXRhdCwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBlYWtfRE9ZIiwgIlNPU19ET1kiLCAiU2Vhc29uX0xlbmd0aCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY2Fub3B5X2hlaWdodCIsICJsYXllciIpDQogICkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGNhc2Vfd2hlbigNCiAgICBIw6FiaXRhdCA9IHN0cl9kZXRlY3QoSMOhYml0YXQsICJQYXN0aXphbHxDZXJ2dW5hbHxncmFzc2xhbmR8bWVhZG93IikgfiAiUiIsDQogICAgSMOhYml0YXQgPSBzdHJfZGV0ZWN0KEjDoWJpdGF0LCAiZm9yZXN0IikgfiAiVCIsDQogICAgSMOhYml0YXQgPSBzdHJfZGV0ZWN0KEjDoWJpdGF0LCAiU2NydWJ8c2NydWJ8U2hydWJsYW5kfHNocnVibGFuZHxzaHJ1YnxIZWF0aGxhbmQiKSB+ICJTIiwNCiAgICBIw6FiaXRhdCA9IHN0cl9kZXRlY3QoSMOhYml0YXQsICJTdWVsb3xTY3JlZXxzY3JlZXxjbGlmZiIpIH4gIlUiLA0KICAgIEjDoWJpdGF0ID0gaXMubmEoSMOhYml0YXQpIH4gIlIiLA0KICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICBFVU5JU2FfMV9kZXNjciA9IGNhc2Vfd2hlbigNCiAgICAgIEVVTklTYV8xID09ICJSIiB+ICJHcmFzc2xhbmRzIiwNCiAgICAgIEVVTklTYV8xID09ICJUIiB+ICJGb3Jlc3RzIGFuZCBvdGhlciB3b29kZWQgbGFuZCIsDQogICAgICBFVU5JU2FfMSA9PSAiUyIgfiAiSGVhdGhsYW5kcywgc2NydWIgYW5kIHR1bmRyYSIsDQogICAgICBFVU5JU2FfMSA9PSAiVSIgfiAiSW5sYW5kIGhhYml0YXRzIHdpdGggbm8gb3IgbGl0dGxlIHNvaWwiKQ0KICAgICkNCmBgYA0KDQojIyMgTkRWSSwgTkRNSQ0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoY29yZGlsbGVyYSwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiTkRWSV9wOTAiLCAiTkRWSV9taW4iLCAiTkRWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EVkkgbWF4IiwgIk5EVkkgcDkwIiwgIk5EVkkgbWluIiwgIk5EVkkgcDEwIikpDQpkaXN0cl9wbG90KGNvcmRpbGxlcmEsDQogICAgICAgICAgIGMoIk5ETUlfbWF4IiwgIk5ETUlfcDkwIiwgIk5ETUlfbWluIiwgIk5ETUlfcDEwIiksIA0KICAgICAgICAgICBjKCJORE1JIG1heCIsICJORE1JIHA5MCIsICJORE1JIG1pbiIsICJORE1JIHAxMCIpKQ0KYGBgDQoNCiMgU2Vzc2lvbiBpbmZvDQoNCmBgYHtyfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCg==